2016-12-29
12:18 PM
此篇文章將介紹在 Apple iOS 平台開發使用的 Swift 3 基礎語法
在某些地方會使用 Java 的類似方法做比對
如此一來便能夠在某些程度上加速對 Apple iOS Swift 3 基礎語法上的了解(當然前提是你懂 Java 囉~)
// 若不使用 ; 結尾也沒問題 swift 使用斷行符號結尾 // 特性 1 // Swift 內的所有物件 除了 class 類別物件以外 // 其他物件全部都是 pass by value (struct, enum) // 也就是說 在作為參數傳遞、指定值的時候 除了 class 類別物件以外 // 其他物件都會複製一份自己 此時若更改複製後的屬性值 將不會變更原本物件的屬性值 // array, dictionary, String, Int, Double 等等都是屬於 struct 的類別 // 特性 2 // struct/enum 若被指定給 constant 變數(let a = someStruct) // 則此 struct/enum 將會變為 immutable 屬性值皆不可異動 // 而在作為參數傳遞時的所有變數都將會是常數( func perform( name:String, value:Int) name 及 value 都是常數 不可異動 ) // 此時若有可能會在方法內修改 struct/enum 的屬性值時 需要在 func 前方加入 mutating 保留字 // 即可異動 struct/enum 的屬性值(詳見 protocol) // 特性 3 // Swift 會猜測變數型態 因此若無特殊需求 不需要在宣告變數時加入型態 // 印出字串 print("Hello, world!"); -- // 宣告變數 var name:Type = value; var name1, name2, name3: Type -- // 宣告常數 let name:Type = value; -- // 宣告 optional var name:Type? = nil; // 宣告為 optional 可以將變數值設定為 nil // 並可使用於 if 條件 判定是否為 nil 值(詳見 if 邏輯判斷式) // 只有 optional 變數可以使用是否為 nil 的判斷 -- // 類型轉換 var floatNum:Float = 2.353; var intNum:Int = Int(floatNum); // print(intNum); -> 2 var string:String = "There are \(intNum) pens."; // print(string); -> There are 2 pens. -- // 宣告陣列 var shoppingList = ["catfish", "water", "tulips", "blue paint"]; // 指定陣列值(若超過目前陣列長度將出現 indexOutOfBound 錯誤) shoppingList[0] = "QQ"; // 新增陣列值 shoppingList.append("TT"); shoppingList += ["AA", "BB"]; // 插入陣列值 shoppingList.insert("CC", at: 2); // 建立空陣列 var emptyArray:[String] = [String](); var emptyArray:[String] = []; -- // 宣告 dictionary var occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic", ] // 指定 dictionary 值(若不存在則新增至 dictionary) occupations["QQ"] = "QQ"; // 建立空 dictionary var emptyDictionary:[String: Float] = [String: Float](); var emptyDictionary:[String: Float] = [:]; -- // Tuple // 新型態與語法 可快速建立可儲存多個值的多個屬性 // 在 func 語法內有進階應用方式(return 值) let x: (String, Int, Double) = ("Hello", 2, 0.85) // set tuple name let (word, number, value) = x print(word) // Hello print(number) // 2 print(value) // 0.85 // with default name let x:( word: String, number: Int, value: Double) = ("Hello", 2, 0.85) print(x.word) // Hello print(x.number) // 2 print(x.value) // 0.85 // rename tuple let (w, n, v) = x print(w) // Hello print(n) // 2 print(v) // 0.85 -- // Range // 儲存開始與結束 Index 的泛型物件 struct Range<T>{ var startIndex: T var endIndex: T } // 且具簡易表示語法 ... 以及 ..< // 範例: 取得陣列中的 subArray let array = ["a", "b", "c", "d"] var subArray1 = array[2...3] // c, d var subArray2 = array[2..<3] // c for i in 27...104 {} // Range 可以列舉(詳見 for 迴圈) -- // 邏輯判斷式 var big:Int = 3; var small:Int = 1; if( big > small ){ print("Yes"); } else{ print("No") } // print -> Yes // optional 應用 var optionalString: String? = "Hello" print(optionalString == nil) var optionalName: String? = "John Appleseed" var greeting = "Hello!" // 若 optionalName 是 nil 則不會進入 if let name = optionalName { greeting = "Hello, \(name)" } else{ greeting = "Hello, nobody" } // switch // 與 java 不同的是 不需使用 break; 中斷 case 區塊 // 因為 swift 進入 case 後就不會再繼續往其他 case 執行了 let vegetable = "red pepper" switch vegetable { case "celery": print("Add some raisins and make ants on a log.") case "cucumber", "watercress": print("That would make a good tea sandwich.") // 將 vegetable 值指定給 x 並判斷 x 是否以 "pepper" 結尾 // 若此處直接使用 vegetable.hasSuffix 會出錯 // 用 let 將 vegetable 值指定給 x 常數後即可使用 switch 判斷 case let x where x.hasSuffix("pepper"): print("Is it a spicy \(x)?") default: print("Everything tastes good in soup.") } // print -> Is it a spicy red pepper? -- // 若值不存在 則給予初始值的方法 let nickName: String? = nil let fullName: String = "John Appleseed" let informalGreeting = "Hi \(nickName ?? fullName)" // print(informalGreeting); -> Hi John Appleseed -- // 基礎 for 迴圈 var total = 0 // 等同於 for( int i = 0; i < 4; i++ ) for i in 0..<4 { total += i } print(total); // print -> 6 // 列出成員 for 迴圈 let interestingNumbers = [ "Prime": [2, 3, 5, 7, 11, 13], "Fibonacci": [1, 1, 2, 3, 5, 8], "Square": [1, 4, 9, 16, 25], ] var largest = 0 // kind 為 key 值, numbers 則是 value 值 // 變數名稱自訂 dictionary 本身無排序 for (kind, numbers) in interestingNumbers { // 使用 in 依序列出陣列成魚 for number in numbers { if number > largest { largest = number } } } print(largest) // print -> 25 // while 迴圈 // 條件成立才會進入區塊 var n = 2 while n < 100 { n = n * 2 } print(n) // print -> 128 // repeat while 迴圈 // 至少執行一次 條件成立才會執行下一次 var m = 2 repeat { m = m * 2 } while m < 100 print(m) // print -> 128 -- // 宣告方法 // func 保留字 // greet 自訂方法名稱 // person: String 變數名稱及類型 // -> String 方法回傳值類型 若無回傳值則不需加入 func greet(person: String, day: String) -> String { return "Hello \(person), today is \(day)." } greet(person: "Bob", day: "Tuesday") // 不需回傳值的方法 func greet() { print("Testing"); } greet(); // 使用自訂變數標籤(Label) // 預設將會使用變數名稱作為標籤 // 若要自訂可在參數名稱前空一格後加入 或是使用 _ 表示不需使用標籤 func greet(_ person: String, on day: String) -> String { return "Hello \(person), today is \(day)." } greet("John", on: "Wednesday") // 回傳 不定數量的多個數值(tuple) func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) { var min = scores[0] var max = scores[0] var sum = 0 for score in scores { if score > max { max = score } else if score < min { min = score } sum += score } return (min, max, sum) } let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9]) // 可使用名稱取值 print(statistics.sum) // 120 // 或是使用索引值取值 print(statistics.2) // 120 // 傳入不定數量的多個數值 func sumOf(numbers: Int...) -> Int { var sum = 0 for number in numbers { sum += number } return sum } sumOf() // 0 sumOf(numbers: 42, 597, 12) // 651 // 巢狀方法 // 可以在方法內再宣告一個方法 func returnFifteen() -> Int { var y = 10 func add() { y += 5 } add() return y } returnFifteen() // 15 // 使用其他方法作為方法的參數值 func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool { for item in list { if condition(item) { return true } } return false } func lessThanTen(number: Int) -> Bool { return number < 10 } var numbers = [20, 19, 7, 12] hasAnyMatches(list: numbers, condition: lessThanTen) // 傳入使用匿名方法參數 // 匿名方法不需宣告名稱及加入 func 保留字 // 僅需定義參數及回傳值即可 但後方需使用 in 保留字作為區塊宣告(相對於一般方法使用的 {}) // map 為傳入方法參數 自訂轉換功能(例如轉換大小寫、更改數值) 回傳轉換後的陣列(原始陣列值不變) // 所有數值 * 3 var numbers = [20, 19, 7, 12] numbers.map({ (number: Int) -> Int in let result = 3 * number return result }) // 更簡易的寫法(由於參數類型已知 回傳類型亦同 因此一併忽略) let mappedNumbers = numbers.map({ number in 3 * number }) print(mappedNumbers) // 所有字串改為大寫 var strings = ["a", "b", "c"]; strings.map({ (string:String) -> String in return s.uppercased() }) // 更簡易的寫法(由於參數類型已知 回傳類型亦同 因此一併忽略) strings.map({ $0.uppercased(); }) // 亦可用於排序方面 let sortedNumbers = numbers.sorted { $0 > $1 } print(sortedNumbers) // 或是篩選陣列中的值 並將所有回傳 true 的值重新組成一個新的陣列 let array = [2, 7, 15, 36, 87, 104] print( array.filter({ $0 > 20 }) ) // [36, 87, 104] // 也可以將 array 內的值做一個運算 let array = [1,2,3,4,5] print( array.reduce(0){ $0 + $1} ) // 15 -- // 宣告 class class Shape { var numberOfSides = 0 func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } // 具有建構子的 class // 建構子為不需加入 func 保留字也不需宣告回傳值類型的方法 名稱必須為 init // 建立物件時必須傳入參數 var ns = NamedShape(name: "PiTT"); // 若加入 deinit 則是當物件要從記憶體內移除時自動被呼叫的方法 // 可以在此處 release 某些物件 class NamedShape { var numberOfSides: Int = 0 var name: String // 建構子 init(name: String) { self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } // 若使用 init? 則表示初始化過程可能會回傳 nil // 也就是回傳一個 Optional 物件 class NamedShape{ var numberOfSides: Int = 0 var name: String // 建構子 init?(name: String) { if( name.isEmpty ) return nil self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } // 繼承 class // 在後方加入父類別 class class Square: NamedShape { // 使用 override 保留字表示此為覆寫父類別方法的方法 // 若未加入 override 保留字 同時方法名稱、傳入參數及回傳直接相同的話 // IDE 會直接報錯 override func simpleDescription() -> String { return "A square with sides of length \(sideLength)." } } // 使用欄位的 getter/setter class Square: NamedShape { var sideLength: Double = 3.0; var perimeter: Double { get { return 3.0 * sideLength } set { // newValue 表示傳入的參數(等號後方的值) // set 本身已隱含此預設名稱 set(newValue) // 亦可自定義名稱 使用 set(value) sideLength = newValue / 3.0 } } } var s: Square = Square(); // print(s.perimeter) -> 9.0 // 使用 willSet // willSet 表示當欄位值在 init 之外改變值的時候都會呼叫的方法 // 例如以下範例為保證兩個欄位值的邊長皆相同的方法 class TriangleAndSquare { var triangle: EquilateralTriangle { willSet { square.sideLength = newValue.sideLength } } var square: Square { willSet { triangle.sideLength = newValue.sideLength } } init(size: Double, name: String) { // 此處設定欄位值並不會觸發 willSet // 所以若修改為 square = Square(sideLength: size * 2, name: name) // 則兩個欄位的邊長將會不相等 square = Square(sideLength: size, name: name) triangle = EquilateralTriangle(sideLength: size, name: name) } } var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape") print(triangleAndSquare.square.sideLength) // 10 print(triangleAndSquare.triangle.sideLength) // 10 triangleAndSquare.square = Square(sideLength: 50, name: "larger square") print(triangleAndSquare.square.sideLength) // 50 print(triangleAndSquare.triangle.sideLength) // 50 // 使用 optional 物件 let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square") // optional 物件使用時必須加入 ? 若物件本身為 nil 則後方表達示將會直接忽略並直接回傳 nil let sideLength = optionalSquare?.sideLength -- // enum // 若使用 Int 類型則 enum 的 RawValue 會預設由 0 開始自動遞增 // 其他類型則必須指定各元素的 RawValue enum Rank: Int { // 若第一個元素設定為 1 則會從 1 開始遞增 case ace = 1 case two, three, four, five, six, seven, eight, nine, ten case jack, queen, king // 亦可宣告方法在 enum 內 func simpleDescription() -> String { switch self { // 注意此處需在前方加入 . 符號 // 因為是在 switch self 的 self 為 Rank 物件 所以下方的 case 可以直接使用 .ace 取得值 // 若在 switch 的目標不是 enum 物件本身(或其元素) 則必須使用 Rank.ace case .ace: return "ace" case .jack: return "jack" case .queen: return "queen" case .king: return "king" default: return String(self.rawValue) } } } let ace = Rank.ace let aceRawValue = ace.rawValue print( ace.simpleDescription() ); // 字串結果的 ace // 亦可使用以下方式取得 enum 元素 Rank(rawValue: 3) // 搭配前面學到的 optional 方法可避免 rawValue 不存在的情況 if let convertedRank = Rank(rawValue: 3) { let threeDescription = convertedRank.simpleDescription() } // 進階應用 // 使用 enum 搭配 struct 簡化程式碼 var result: Double = 0.0 enum Operation{ // 可傳遞 associate value 以便之後取得 需注意此參數與 rawValue 是不同的意義 類似於 optional 的 associate value case Constant(Double) case UnaryOperation } var operations: Dictionary<String, Operations> = [ "pi" : .Constant(M_PI) "e" : .Constant(M_E) ] func performOperation(symbol: String){ if( let operation == operations[symbol] ){ switch operation { // 使用 let value 取得 associate value case .Constant(let value): result = value; default: break; } } } -- // Struct // 與 class 非常類似 亦可包含方法與欄位 僅可繼承 Protocal // 但在作為參數傳遞時 struct 將會複製一份自己作為傳輸 而 class 則是僅傳遞記憶體位置 // 因此 struct 傳遞後會有多個物件存在於不同記憶體位置 // 而 class 則只會有一個記憶體位置 struct Card { var rank: Rank var suit: Suit func simpleDescription() -> String { return "The \(rank.simpleDescription()) of \(suit.simpleDescription())" } } -- // Protocal // 類似於 Java 的 Interface protocol ExampleProtocol { var simpleDescription: String { get } // mutating 表示此方法可能會在方法內修改 struct/enum 的屬性值 mutating func adjust() } class SimpleClass: ExampleProtocol { var simpleDescription: String = "A very simple class." var anotherProperty: Int = 69105 // 此處覆寫的方法前方不需加入 mutating 保留字 因為 class 本身原本就可以覆寫自己的屬性值 func adjust() { simpleDescription += " Now 100% adjusted." } } struct SimpleStructure: ExampleProtocol { var simpleDescription: String = "A simple structure" // Struct 則必須加上 mutating 保留字 以便覆寫屬性值 mutating func adjust() { simpleDescription += " (adjusted)" } } -- // Extension // 可以擴充原本存在的 Class 功能 extension Int: ExampleProtocol { var simpleDescription: String { return "The number \(self)" } mutating func adjust() { self += 42 } } print(7.simpleDescription) // The number 7 -- // 多型 // 與 Java 相同 亦可直接將變數類型指定為 Protocal // 可使用的方法只有 Protocal 內定義的方法 let protocolValue: ExampleProtocol = a print(protocolValue.simpleDescription) -- // 錯誤處理 // 直接繼承 Error Protocal enum PrinterError: Error { case outOfPaper case noToner case onFire } // 在會拋出異常的方法參數後方加入 throws 保留字 表示此方法會拋出異常 func send(job: Int, toPrinter printerName: String) throws -> String { if printerName == "Never Has Toner" { throw PrinterError.noToner } return "Job sent" } // 使用 do-catch 控制會拋出異常的流程 // 須注意使用時在會拋出的方法前方需加入 try 保留字 do { let printerResponse = try send(job: 1040, toPrinter: "Never Has Toner") print(printerResponse) } catch { print(error) } // 亦可使用多個 catch 處理不同的異常類型 do { let printerResponse = try send(job: 1440, toPrinter: "Gutenberg") print(printerResponse) } // 處理 PrinterError.onFire 這項錯誤 catch PrinterError.onFire { print("I'll just put this over here, with the rest of the fire.") } // 處理所有 PrinterError 類型的錯誤 catch let printerError as PrinterError { print("Printer error: \(printerError).") } // 處理其他所有錯誤 catch { print(error) } // 或是使用 optional 方式處理異常 // 會拋出異常的方法將回傳 nil let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler") let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner") // nil -- // defer // 類似於 Java 的 finally // 會在 function 的所有內容執行完成後進入 var fridgeIsOpen = false let fridgeContent = ["milk", "eggs", "leftovers"] func fridgeContains(_ food: String) -> Bool { fridgeIsOpen = true defer { fridgeIsOpen = false } let result = fridgeContent.contains(food) return result } fridgeContains("banana") print(fridgeIsOpen) // false -- // 泛型 func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] { var result = [Item]() // 此處使用 _ ,因為實際是不需使用到此變數 所以使用 _ 直接忽略變數宣告 類似於參數傳遞的 _ 標籤 for _ in 0..<numberOfTimes { result.append(item) } return result } makeArray(repeating: "knock", numberOfTimes:4) // ["knock", "knock", "knock", "knock"] -- // where // 可加在 function 後方表示僅有符合此條件者才可進入 function 內 // 例如 在定義幾乎完全相同的 function 時( 如名稱、回傳類型相同 且參數皆為泛型時 ) // 在 Java 內會出現無法識別方法的編譯錯誤 // 而 Swift 則可以使用 where 制定不同泛型類型對應的不同處理方法 // Sequence 表示陣列類型的父類別 // where 表示 T, U 皆需實作 == 比對的方法(Equatable) 且兩者內容元素的類型相同才可進入此方法 func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Int where T.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element { for lhsItem in lhs { for rhsItem in rhs { if lhsItem == rhsItem { return 0 } } } return 1 } // 回傳為 Int 類型的相同泛型參數方法 func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Int { return 2 } // 回傳為 Bool 類型的相同泛型參數方法 func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool { return false } // 以下三者將各自呼叫不同的方法 anyCommonElements([1, 2, 3], [3]) // 0 var a:Int = anyCommonElements([1, 2, 3], ["String"]) // 2 var b:Bool = anyCommonElements([1, 2, 3], ["String"]) // false -- // 執行緒 let queue:OperationQueue = OperationQueue(); queue.addOperation { // Code } -- // lazy 保留字 // 會在真正使用到此變數時才進行初始化動作(僅能使用在 var 變數 不可使用在 let 常數) lazy var calculator = Calculator() // not initialized yet // 若要在給訂初始值時使用到 self 物件 可以使用 lazy 保留字 // 若未加入 lazy 則會出現 self 尚未被初始化錯誤 lazy var t: Type = { var r = self.someProperty * 2 return r } -- // AnyObject // 如字面意義 代表任何物件型態 var any:AnyObject; // 是否為 SomeObjecy 類型 if any is SomeObject{ } // 將值指定給 foo 前確認型態(Casting) if let foo = any as? SomeObject{ } // Assert // Debug 時使用 在 release 時會自動忽略 // autoclosure 使用時不需加入 {} assert( () -> Bool, "message") assert( validate(), "Validate failed" )
各項資料連結
Swift Tour