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