一 Swift教程(筆記)

基礎(chǔ)部分(The Basics)

  • 當(dāng)推斷浮點(diǎn)數(shù)的類型時(shí),Swift 總是會選擇Double而不是Float。
  • 結(jié)合數(shù)字類常量和變量不同于結(jié)合數(shù)字類字面量。字面量3可以直接和字面量0.14159相加,因?yàn)閿?shù)字字面量本身沒有明確的類型。它們的類型只在編譯器需要求值的時(shí)候被推測。
  • 類型別名(type aliases)就是給現(xiàn)有類型定義另一個(gè)名字。你可以使用typealias關(guān)鍵字來定義類型別名。
    typealias AudioSample = UInt16
    var maxAmplitudeFound = AudioSample.min
  • 元組(tuples)把多個(gè)值組合成一個(gè)復(fù)合值。元組內(nèi)的值可以是任意類型,并不要求是相同類型。
    let http404Error = (404, "Not Found")
    // http404Error 的類型是 (Int, String),值是 (404, "Not Found")
    let (statusCode, statusMessage) = http404Error
    print("The status code is (statusCode)")
    // 輸出 "The status code is 404"
    print("The status message is (statusMessage)")
    // 輸出 "The status message is Not Found
    如果你只需要一部分元組值,分解的時(shí)候可以把要忽略的部分用下劃線(_)標(biāo)記:
    let (justTheStatusCode, _) = http404Error
    print("The status code is (justTheStatusCode)")
    // 輸出 "The status code is 404
    此外,你還可以通過下標(biāo)來訪問元組中的單個(gè)元素,下標(biāo)從零開始:
    print("The status code is (http404Error.0)")
    // 輸出 "The status code is 404"
    print("The status message is (http404Error.1)")
    // 輸出 "The status message is Not Found
    你可以在定義元組的時(shí)候給單個(gè)元素命名:
    let http200Status = (statusCode: 200, description: "OK")
    給元組中的元素命名后,你可以通過名字來獲取這些元素的值:
    print("The status code is (http200Status.statusCode)")
    // 輸出 "The status code is 200"
    print("The status message is (http200Status.description)")
    // 輸出 "The status message is OK
  • 使用可選類型(optionals)來處理值可能缺失的情況??蛇x類型表示:
    有值,等于 x;或者,沒有值
    如果你聲明一個(gè)可選常量或者變量但是沒有賦值,它們會自動被設(shè)置為nil:
    var surveyAnswer: String?
    // surveyAnswer 被自動設(shè)置為 nil”

注意:
nil不能用于非可選的常量和變量。如果你的代碼中有常量或者變量需要處理值缺失的情況,請把它們聲明成對應(yīng)的可選類型。
Swift 的nil和 Objective-C 中的nil并不一樣。在 Objective-C 中,nil是一個(gè)指向不存在對象的指針。在 Swift 中,nil不是指針——它是一個(gè)確定的值,用來表示值缺失。任何類型的可選狀態(tài)都可以被設(shè)置為nil,不只是對象類型。
空字符串(如"")和一個(gè)值為nil的可選類型的字符串是兩個(gè)完全不同的概念。

當(dāng)你確定可選類型確實(shí)包含值之后,你可以在可選的名字后面加一個(gè)感嘆號(!)來獲取值。
有時(shí)候在程序架構(gòu)中,第一次被賦值之后,可以確定一個(gè)可選類型總會有值。在這種情況下,每次都要判斷和解析可選值是非常低效的,因?yàn)榭梢源_定它總會有值。這種類型的可選狀態(tài)被定義為隱式解析可選類型(implicitly unwrapped optionals)。把想要用作可選的類型的后面的問號(String?)改成感嘆號(String!)來聲明一個(gè)隱式解析可選類型。
let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要驚嘆號來獲取值

  let assumedString: String! = "An implicitly unwrapped optional string."
  let implicitString: String = assumedString  // 不需要感嘆號
  • 你可以使用全局assert(_:_file:line:)函數(shù)來寫一個(gè)斷言。向這個(gè)函數(shù)傳入一個(gè)結(jié)果為true或者false的表達(dá)式以及一條信息,當(dāng)表達(dá)式的結(jié)果為false的時(shí)候這條信息會被顯示并終止程序:
    let age = -3
    assert(age >= 0, "A person's age cannot be less than zero")
    //因?yàn)閍ge<0,所以斷言會觸發(fā)

基本運(yùn)算符(Basic Operators)

  • 在對負(fù)數(shù) b 求余時(shí),b 的符號會被忽略。這意味著a % ba % -b 的結(jié)果是相同的。
  • 空合運(yùn)算符(a ?? b)將對可選類型a進(jìn)行空判斷,如果a包含一個(gè)值就進(jìn)行解封,否則就返回一個(gè)默認(rèn)值b.這個(gè)運(yùn)算符有兩個(gè)條件:1.表達(dá)式a必須是Optional類型 2.默認(rèn)值b的類型必須要和a存儲值的類型保持一致”
  • 閉區(qū)間運(yùn)算符a...b)定義一個(gè)包含從ab(包括ab)的所有值的區(qū)間,b必須大于等于a。
    半開區(qū)間a..<b)定義一個(gè)從ab但不包括b的區(qū)間。 之所以稱為半開區(qū)間,是因?yàn)樵搮^(qū)間包含第一個(gè)值而不包括最后的值。
  • 邏輯非(!a
    邏輯與(a && b
    邏輯或(a || b

字符串和字符(Strings and Characters)

  • Swift 的String類型是值類型。 如果您創(chuàng)建了一個(gè)新的字符串,那么當(dāng)其進(jìn)行常量、變量賦值操作,或在函數(shù)/方法中傳遞時(shí),會進(jìn)行值拷貝。 任何情況下,都會對已有字符串值創(chuàng)建新副本,并對該新副本進(jìn)行傳遞或賦值操作。
  • 您可通過for-in循環(huán)來遍歷字符串中的characters屬性來獲取每一個(gè)字符的值:
    for character in "Dog!??".characters {
    print(character)
    }
    // D
    // o
    // g
    // !
    // ??
  • 您可以用append()方法將一個(gè)字符附加到一個(gè)字符串變量的尾部:
    let welcome = "hello there"
    let exclamationMark: Character = "!"
    welcome.append(exclamationMark)
    // welcome 現(xiàn)在等于 "hello there!

注意: 您不能將一個(gè)字符串或者字符添加到一個(gè)已經(jīng)存在的字符變量上,因?yàn)樽址兞恐荒馨粋€(gè)字符。

  • 訪問和修改字符串 (Accessing and Modifying a String)

Swift 的字符串不能用整數(shù)(integer)做索引。
使用startIndex屬性可以獲取一個(gè)String的第一個(gè)Character的索引。使用endIndex屬性可以獲取最后一個(gè)Character的后一個(gè)位置的索引。因此,endIndex屬性不能作為一個(gè)字符串的有效下標(biāo)。如果String是空串,startIndexendIndex是相等的。
通過調(diào)用String.Indexpredecessor()方法,可以立即得到前面一個(gè)索引,調(diào)用successor()方法可以立即得到后面一個(gè)索引。任何一個(gè)String的索引都可以通過鎖鏈作用的這些方法來獲取另一個(gè)索引,也可以調(diào)用advancedBy(_:)方法來獲取。但如果嘗試獲取出界的字符串索引,就會拋出一個(gè)運(yùn)行時(shí)錯(cuò)誤。
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.endIndex.predecessor()]
// !
greeting[greeting.startIndex.successor()]
// u
let index = greeting.startIndex.advancedBy(7)
greeting[index]
// a
使用characters屬性的indices屬性會創(chuàng)建一個(gè)包含全部索引的范圍(Range),用來在一個(gè)字符串中訪問單個(gè)字符。
for index in greeting.characters.indices {
print("(greeting[index]) ", terminator: " ")
}
// 打印輸出 "G u t e n T a g !
調(diào)用insert(_:atIndex:)方法可以在一個(gè)字符串的指定索引插入一個(gè)字符。
var welcome = "hello"
welcome.insert("!", atIndex: welcome.endIndex)
// welcome now 現(xiàn)在等于 "hello!"
調(diào)用insertContentsOf(_:at:)方法可以在一個(gè)字符串的指定索引插入一個(gè)字符串。
welcome.insertContentsOf(" there".characters, at: welcome.endIndex.predecessor())
// welcome 現(xiàn)在等于 "hello there!"
調(diào)用removeAtIndex(_:)方法可以在一個(gè)字符串的指定索引刪除一個(gè)字符。
welcome.removeAtIndex(welcome.endIndex.predecessor())
// welcome 現(xiàn)在等于 "hello there"
調(diào)用removeRange(_:)方法可以在一個(gè)字符串的指定索引刪除一個(gè)子字符串。

  let range = welcome.endIndex.advancedBy(-6)..<welcome.endIndex
  welcome.removeRange(range)
  // welcome 現(xiàn)在等于 "hello"
  • 比較字符串 (Comparing Strings)

通過調(diào)用字符串的hasPrefix(:)/hasSuffix(:)方法來檢查字符串是否擁有特定前綴/后綴,兩個(gè)方法均接收一個(gè)String類型的參數(shù),并返回一個(gè)布爾值。
let romeoAndJuliet = [
"Act 1 Scene 1: Verona, A public place",
"Act 1 Scene 2: Capulet's mansion",
"Act 1 Scene 3: A room in Capulet's mansion",
"Act 1 Scene 4: A street outside Capulet's mansion",
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
"Act 2 Scene 1: Outside Capulet's mansion",
"Act 2 Scene 2: Capulet's orchard",
"Act 2 Scene 3: Outside Friar Lawrence's cell",
"Act 2 Scene 4: A street in Verona",
"Act 2 Scene 5: Capulet's mansion",
"Act 2 Scene 6: Friar Lawrence's cell"
]
您可以調(diào)用hasPrefix(_:)方法來計(jì)算話劇中第一幕的場景數(shù):

  var act1SceneCount = 0
  for scene in romeoAndJuliet {
      if scene.hasPrefix("Act 1 ") {
          ++act1SceneCount
      }
  }
  print("There are \(act1SceneCount) scenes in Act 1")
  // 打印輸出 "There are 5 scenes in Act 1"

相似地,您可以用hasSuffix(_:)方法來計(jì)算發(fā)生在不同地方的場景數(shù):

  var mansionCount = 0
  var cellCount = 0
  for scene in romeoAndJuliet {
      if scene.hasSuffix("Capulet's mansion") {
          ++mansionCount
      } else if scene.hasSuffix("Friar Lawrence's cell") {
          ++cellCount
      }
  }
  print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
  // 打印輸出 "6 mansion scenes; 2 cell scenes

集合類型 (Collection Types)

  • 數(shù)組(Arrays)是有序數(shù)據(jù)的集。集合(Sets)是無序無重復(fù)數(shù)據(jù)的集。字典(Dictionaries)是無序的鍵值對的集。
  • 使用布爾值屬性isEmpty作為檢查數(shù)組count屬性的值是否為 0 的捷徑。
    可以利用下標(biāo)來一次改變一系列數(shù)據(jù)值,即使新數(shù)據(jù)和原有數(shù)據(jù)的數(shù)量不一樣。“下面的例子把"Chocolate Spread","Cheese",和"Butter"替換為"Bananas"和 "Apples":
    var shoppingList : [String] = ["Eggs","Milk","Flour","Baking Powder","Chocolate Spread", "Cheese", "Butter"]
    shoppingList[4...6] = ["Bananas", "Apples"]
    調(diào)用數(shù)組的insert(_:atIndex:)方法來在某個(gè)具體索引值之前添加數(shù)據(jù)項(xiàng):
    shoppingList.insert("Maple Syrup", atIndex: 0)
    // shoppingList 現(xiàn)在有7項(xiàng)
    // "Maple Syrup" 現(xiàn)在是這個(gè)列表中的第一項(xiàng)”
    使用removeAtIndex(_:)方法來移除數(shù)組中的某一項(xiàng)。這個(gè)方法把數(shù)組在特定索引值中存儲的數(shù)據(jù)項(xiàng)移除并且返回這個(gè)被移除的數(shù)據(jù)項(xiàng):
    let mapleSyrup = shoppingList.removeAtIndex(0)
    // 索引值為0的數(shù)據(jù)項(xiàng)被移除
    // shoppingList 現(xiàn)在只有6項(xiàng),而且不包括 Maple Syrup
    // mapleSyrup 常量的值等于被移除數(shù)據(jù)項(xiàng)的值 "Maple Syrup
    如果我們只想把數(shù)組中的最后一項(xiàng)移除,可以使用removeLast()方法而不是removeAtIndex(_:)方法來避免我們需要獲取數(shù)組的count屬性。
    如果我們同時(shí)需要每個(gè)數(shù)據(jù)項(xiàng)的值和索引值,可以使用enumerate()方法來進(jìn)行數(shù)組遍歷。enumerate()返回一個(gè)由每一個(gè)數(shù)據(jù)項(xiàng)索引值和數(shù)據(jù)值組成的元組:
    for (index, value) in shoppingList.enumerate() {
    print("Item (String(index + 1)): (value)")
    }
  • 使用contains(_:)方法去檢查Set中是否包含一個(gè)特定的值。
    Swift 的Set類型沒有確定的順序,為了按照特定順序來遍歷一個(gè)Set中的值可以使用sort()方法,它將根據(jù)提供的序列返回一個(gè)有序集合:
    let favoriteGenres: Set = ["Jazz","Classical","Hip hop"]
    for genre in favoriteGenres.sort() {
    print("(genre)")
    }
    // prints "Classical"
    // prints "Hip hop"
    // prints "Jazz”

1.png

使用intersect(_:)方法根據(jù)兩個(gè)集合中都包含的值創(chuàng)建的一個(gè)新的集合。
使用exclusiveOr(_:)方法根據(jù)在一個(gè)集合中但不在兩個(gè)集合中的值創(chuàng)建一個(gè)新的集合。
使用union(_:)方法根據(jù)兩個(gè)集合的值創(chuàng)建一個(gè)新的集合。
使用subtract(_:)方法根據(jù)不在該集合中的值創(chuàng)建一個(gè)新的集合。

  • 字典updateValue(_:forKey:)方法可以設(shè)置或者更新特定鍵對應(yīng)的值,并且返回更新值之前的原值。
    removeValueForKey(_:)方法用來在字典中移除鍵值對。這個(gè)方法在鍵值對存在的情況下會移除該鍵值對并且返回被移除的值或者在沒有值的情況下返回nil。
    如果我們只是需要使用某個(gè)字典的鍵集合或者值集合來作為某個(gè)接受Array實(shí)例的 API 的參數(shù),可以直接使用keys或者values屬性構(gòu)造一個(gè)新數(shù)組。

控制流(Control Flow)

  • For 循環(huán)

如果你不需要知道區(qū)間序列內(nèi)每一項(xiàng)的值,你可以使用下劃線(_)替代變量名來忽略對值的訪問:
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("(base) to the power of (power) is (answer)")
// 輸出 "3 to the power of 10 is 59049

  • 條件語句

不像 C 語言,Swift 允許多個(gè) case 匹配同一個(gè)值。實(shí)際上,在下面例子中,點(diǎn)(0, 0)可以匹配所有四個(gè) case。但是,如果存在多個(gè)匹配,那么只會執(zhí)行第一個(gè)被匹配到的 case 分支??紤]點(diǎn)(0, 0)會首先匹配case (0, 0),因此剩下的能夠匹配(0, 0)的 case 分支都會被忽視掉:
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("(0, 0) is at the origin")
case (_, 0):
print("((somePoint.0), 0) is on the x-axis")
case (0, _):
print("(0, (somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
print("((somePoint.0), (somePoint.1)) is inside the box")
default:
print("((somePoint.0), (somePoint.1)) is outside of the box")
}
// 輸出 "(1, 1) is inside the box
case 分支的模式允許將匹配的值綁定到一個(gè)臨時(shí)的常量或變量,這些常量或變量在該 case 分支里就可以被引用了——這種行為被稱為值綁定(value binding)。下面的例子展示了如何在一個(gè)(Int, Int)類型的元組中使用值綁定來分類下圖中的點(diǎn)(x, y):
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
print("on the x-axis with an x value of (x)")
case (0, let y):
print("on the y-axis with a y value of (y)")
case let (x, y):
print("somewhere else at ((x), (y))")
}
// 輸出 "on the x-axis with an x value of 2
case 分支的模式可以使用where語句來判斷額外的條件。下面的例子把下圖中的點(diǎn)(x, y)進(jìn)行了分類:
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
print("((x), (y)) is on the line x == y")
case let (x, y) where x == -y:
print("((x), (y)) is on the line x == -y")
case let (x, y):
print("((x), (y)) is just some arbitrary point")
}
// 輸出 "(1, -1) is on the line x == -y

  • 控制轉(zhuǎn)移語句

continue語句告訴一個(gè)循環(huán)體立刻停止本次循環(huán)迭代,重新開始下次循環(huán)迭代。
break語句會立刻結(jié)束整個(gè)控制流的執(zhí)行。當(dāng)你想要更早的結(jié)束一個(gè)switch代碼塊或者一個(gè)循環(huán)體時(shí),你都可以使用break語句。
在 Swift 中,你可以在循環(huán)體和switch代碼塊中嵌套循環(huán)體和switch代碼塊來創(chuàng)造復(fù)雜的控制流結(jié)構(gòu)。然而,循環(huán)體和switch代碼塊兩者都可以使用break語句來提前結(jié)束整個(gè)方法體。因此,顯式地指明break語句想要終止的是哪個(gè)循環(huán)體或者switch代碼塊,會很有用。類似地,如果你有許多嵌套的循環(huán)體,顯式指明continue語句想要影響哪一個(gè)循環(huán)體也會非常有用。為了實(shí)現(xiàn)這個(gè)目的,你可以使用標(biāo)簽來標(biāo)記一個(gè)循環(huán)體或者switch代碼塊,當(dāng)使用break或者continue時(shí),帶上這個(gè)標(biāo)簽,可以控制該標(biāo)簽代表對象的中斷或者執(zhí)行。

  label name: while condition {
      statements
  }

下面的例子是在一個(gè)帶有標(biāo)簽的while循環(huán)體中調(diào)用breakcontinue語句:
let finalSquare = 25
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0
gameLoop: while square != finalSquare {
if ++diceRoll == 7 { diceRoll = 1 }
switch square + diceRoll {
case finalSquare:
// 到達(dá)最后一個(gè)方塊,游戲結(jié)束
break gameLoop
case let newSquare where newSquare > finalSquare:
// 超出最后一個(gè)方塊,再擲一次骰子
continue gameLoop
default:
// 本次移動有效
square += diceRoll
square += board[square]
}
}
print("Game over!")
如果骰子數(shù)剛好使玩家移動到最終的方格里,游戲結(jié)束。break gameLoop語句跳轉(zhuǎn)控制去執(zhí)行while循環(huán)體后的第一行代碼,游戲結(jié)束。
如果骰子數(shù)將會使玩家的移動超出最后的方格,那么這種移動是不合法的,玩家需要重新擲骰子。continue gameLoop語句結(jié)束本次while循環(huán)的迭代,開始下一次循環(huán)迭代。

注意:
如果上述的break語句沒有使用gameLoop標(biāo)簽,那么它將會中斷switch代碼塊而不是while循環(huán)體。使用gameLoop標(biāo)簽清晰的表明了break想要中斷的是哪個(gè)代碼塊。 同時(shí)請注意,當(dāng)調(diào)用continue gameLoop去跳轉(zhuǎn)到下一次循環(huán)迭代時(shí),這里使用gameLoop標(biāo)簽并不是嚴(yán)格必須的。因?yàn)樵谶@個(gè)游戲中,只有一個(gè)循環(huán)體,所以continue語句會影響到哪個(gè)循環(huán)體是沒有歧義的。然而,continue語句使用gameLoop標(biāo)簽也是沒有危害的。這樣做符合標(biāo)簽的使用規(guī)則,同時(shí)參照旁邊的break gameLoop`,能夠使游戲的邏輯更加清晰和易于理解。

  • 提前退出

if語句一樣,guard的執(zhí)行取決于一個(gè)表達(dá)式的布爾值。我們可以使用guard語句來要求條件必須為真時(shí),以執(zhí)行guard語句后的代碼。不同于if語句,一個(gè)guard語句總是有一個(gè)else分句,如果條件不為真則執(zhí)行else分句中的代碼。

  • 檢測API可用性

    if #available(iOS 10, macOS 10.12, *) {
        // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
    } else {
        // 使用先前版本的 iOS 和 macOS 的 API
    }
    

以上可用性條件指定,在iOS中,if語句的代碼塊僅僅在 iOS 10 及更高的系統(tǒng)下運(yùn)行;在 macOS中,僅在 macOS 10.12 及更高才會運(yùn)行。最后一個(gè)參數(shù),*,是必須的,用于指定在所有其它平臺中,如果版本號高于你的設(shè)備指定的最低版本,if語句的代碼塊將會運(yùn)行。


函數(shù)(Functions)

  • 可選元組類型如(Int, Int)?與元組包含可選類型如(Int?, Int?)是不同的.可選的元組類型,整個(gè)元組是可選的,而不只是元組中的每個(gè)元素值。
  • 函數(shù)參數(shù)名稱

可以在局部參數(shù)名前指定外部參數(shù)名,中間以空格分隔。
如果你不想為第二個(gè)及后續(xù)的參數(shù)設(shè)置外部參數(shù)名,用一個(gè)下劃線(_)代替一個(gè)明確的參數(shù)名。因?yàn)榈谝粋€(gè)參數(shù)默認(rèn)忽略其外部參數(shù)名稱,顯式地寫下劃線是多余的。
一個(gè)可變參數(shù)(variadic parameter)可以接受零個(gè)或多個(gè)值。函數(shù)調(diào)用時(shí),你可以用可變參數(shù)來指定函數(shù)參數(shù)可以被傳入不確定數(shù)量的輸入值。通過在變量類型名后面加入(...)的方式來定義可變參數(shù):
func arithmeticMean(numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers

注意:
一個(gè)函數(shù)最多只能有一個(gè)可變參數(shù)。
通過在參數(shù)名前加關(guān)鍵字var 來定義變量參數(shù)。

如果想要一個(gè)函數(shù)可以修改參數(shù)的值,并且想要在這些修改在函數(shù)調(diào)用結(jié)束后仍然存在,那么就應(yīng)該把這個(gè)參數(shù)定義為輸入輸出參數(shù)(In-Out Parameters)。定義一個(gè)輸入輸出參數(shù)時(shí),在參數(shù)定義前加inout 關(guān)鍵字。一個(gè)輸入輸出參數(shù)有傳入函數(shù)的值,這個(gè)值被函數(shù)修改,然后被傳出函數(shù),替換原來的值。你只能傳遞變量給輸入輸出參數(shù)。你不能傳入常量或者字面量(literal value),因?yàn)檫@些量是不能被修改的。當(dāng)傳入的參數(shù)作為輸入輸出參數(shù)時(shí),需要在參數(shù)名前加 & 符,表示這個(gè)值可以被函數(shù)修改。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}

  var someInt = 3
  var anotherInt = 107
  swapTwoInts(&someInt, &anotherInt)
  print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
  // Prints "someInt is now 107, and anotherInt is now 3

注意:
函數(shù)是獨(dú)立的,而方法是函數(shù)封裝在類,結(jié)構(gòu)或者枚舉中的函數(shù)。


閉包(Closures)

  • 閉包表達(dá)式語法有如下一般形式:
    { (parameters) -> returnType in
    statements
    }
    閉包可以省略它的參數(shù)的type 和返回值的type. 如果省略了參數(shù)和參數(shù)類型,就也要省略 in關(guān)鍵字。 如果被省略的type 無法被編譯器獲知(inferred) ,那么就會拋出編譯錯(cuò)誤。
    閉包可以省略參數(shù),轉(zhuǎn)而在方法體(statement)中使用 $0, $1, $2 來引用出現(xiàn)的第一個(gè),第二個(gè),第三個(gè)參數(shù)。
    如果閉包中只包含了一個(gè)表達(dá)式,那么該表達(dá)式就會自動成為該閉包的返回值。
    函數(shù):
    let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
    func backwards(s1: String, s2: String) -> Bool {
    return s1 > s2
    }
    var reversed = names.sort(backwards)
    // reversed 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    閉包:
    reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } )

  • 如果您需要將一個(gè)很長的閉包表達(dá)式作為最后一個(gè)參數(shù)傳遞給函數(shù),可以使用尾隨閉包來增強(qiáng)函數(shù)的可讀性。尾隨閉包是一個(gè)書寫在函數(shù)括號之后的閉包表達(dá)式,函數(shù)支持將其作為最后一個(gè)參數(shù)調(diào)用。
    func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函數(shù)體部分
    }

    // 以下是不使用尾隨閉包進(jìn)行函數(shù)調(diào)用
    someFunctionThatTakesAClosure({
        // 閉包主體部分
    })
    
    // 以下是使用尾隨閉包進(jìn)行函數(shù)調(diào)用
    someFunctionThatTakesAClosure() {
        // 閉包主體部分
    }
    

枚舉(Enumerations)

  • 使用enum關(guān)鍵詞來創(chuàng)建枚舉并且把它們的整個(gè)定義放在一對大括號內(nèi):
    enum SomeEnumeration {
    // 枚舉定義放在這里
    }
    下面是用枚舉表示指南針?biāo)膫€(gè)方向的例子:
    enum CompassPoint {
    case North
    case South
    case East
    case West
    }
  • 如果一個(gè)枚舉成員的所有關(guān)聯(lián)值都被提取為常量,或者都被提取為變量,為了簡潔,你可以只在成員名稱前標(biāo)注一個(gè)let或者var。

類和結(jié)構(gòu)體(Classes and Structures)

  • 結(jié)構(gòu)體和枚舉是值類型

值類型被賦予給一個(gè)變量、常量或者被傳遞給一個(gè)函數(shù)的時(shí)候,其值會被拷貝。
struct Resolution {
var width = 0
var height = 0
}
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
// 這里,將會顯示cinema的width屬性確已改為了2048:
print("cinema is now (cinema.width) pixels wide")
// 輸出 "cinema is now 2048 pixels wide
print("hd is still (hd.width) pixels wide")
// 輸出 "hd is still 1920 pixels wide
cinemawidth屬性確已改為了2048,然而,初始的hd實(shí)例中width屬性還是1920。

  • 類是引用類型

與值類型不同,引用類型在被賦予到一個(gè)變量、常量或者被傳遞到一個(gè)函數(shù)時(shí),其值不會被拷貝。因此,引用的是已存在的實(shí)例本身而不是其拷貝。
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
let tenEighty = VideoMode()
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
print("The frameRate property of tenEighty is now (tenEighty.frameRate)")
// 輸出 "The frameRate property of theEighty is now 30.0
因?yàn)轭愂且妙愋?,所?code>tenEight和alsoTenEight實(shí)際上引用的是相同的VideoMode實(shí)例。換句話說,它們是同一個(gè)實(shí)例的兩種叫法。通過查看tenEightyframeRate屬性,我們會發(fā)現(xiàn)它正確的顯示了所引用的VideoMode實(shí)例的新幀率,其值為30.0。
需要注意的是tenEightyalsoTenEighty被聲明為常量而不是變量。然而你依然可以改變tenEighty.frameRate和alsoTenEighty.frameRate,因?yàn)?code>tenEighty和alsoTenEighty這兩個(gè)常量的值并未改變。它們并不“存儲”這個(gè)VideoMode實(shí)例,而僅僅是對VideoMode實(shí)例的引用。所以,改變的是被引用的VideoModeframeRate屬性,而不是引用VideoMode的常量的值。

  • 如果能夠判定兩個(gè)常量或者變量是否引用同一個(gè)類實(shí)例將會很有幫助。為了達(dá)到這個(gè)目的,Swift 內(nèi)建了兩個(gè)恒等運(yùn)算符
    等價(jià)于(===
    不等價(jià)于(!==
  • Swift 中,許多基本類型,諸如StringArrayDictionary類型均以結(jié)構(gòu)體的形式實(shí)現(xiàn)。這意味著被賦值給新的常量或變量,或者被傳入函數(shù)或方法中時(shí),它們的值會被拷貝。
    Objective-C 中NSString,NSArrayNSDictionary類型均以類的形式實(shí)現(xiàn),而并非結(jié)構(gòu)體。它們在被賦值或者被傳入函數(shù)或方法時(shí),不會發(fā)生值拷貝,而是傳遞現(xiàn)有實(shí)例的引用。

屬性 (Properties)

  • 結(jié)構(gòu)體(struct)屬于值類型。當(dāng)值類型的實(shí)例被聲明為常量的時(shí)候,它的所有屬性也就成了常量。
    屬于引用類型的類(class)則不一樣。把一個(gè)引用類型的實(shí)例賦給一個(gè)常量后,仍然可以修改該實(shí)例的變量屬性。
  • 延遲存儲屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時(shí)候才會計(jì)算其初始值的屬性。在屬性聲明前使用lazy來標(biāo)示一個(gè)延遲存儲屬性,只有var才能用lazy初始化
  • 存儲屬性

類和結(jié)構(gòu)體在創(chuàng)建實(shí)例時(shí),必須為所有存儲型屬性設(shè)置合適的初始值。

  • 計(jì)算屬性

除存儲屬性外,類、結(jié)構(gòu)體和枚舉可以定義計(jì)算屬性。計(jì)算屬性不直接存儲值,而是提供一個(gè)getter 和一個(gè)可選的 setter,來間接獲取和設(shè)置其他屬性或變量的值。

  struct Point {
      var x = 0.0, y = 0.0
  }
  struct Size {
      var width = 0.0, height = 0.0
  }
    struct Rect {
      var origin = Point()
      var size = Size()
      var center: Point {
          get {
              let centerX = origin.x + (size.width / 2)
              let centerY = origin.y + (size.height / 2)
              return Point(x: centerX, y: centerY)
          }
          set(newCenter) {
              origin.x = newCenter.x - (size.width / 2)
              origin.y = newCenter.y - (size.height / 2)
          }
      }
  }
  var square = Rect(origin: Point(x: 0.0, y: 0.0),
      size: Size(width: 10.0, height: 10.0))
  let initialSquareCenter = square.center
  square.center = Point(x: 15.0, y: 15.0)
  print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
  // 輸出 "square.origin is now at (10.0, 10.0)

如果計(jì)算屬性的 setter 沒有定義表示新值的參數(shù)名,則可以使用默認(rèn)名稱newValue。
必須使用var關(guān)鍵字定義計(jì)算屬性,包括只讀計(jì)算屬性,因?yàn)樗鼈兊闹挡皇枪潭ǖ摹?code>let關(guān)鍵字只用來聲明常量屬性,表示初始化后再也無法修改的值。
只有 getter 沒有 setter 的計(jì)算屬性就是只讀計(jì)算屬性。只讀計(jì)算屬性總是返回一個(gè)值,可以通過點(diǎn)運(yùn)算符訪問,但不能設(shè)置新的值?!爸蛔x計(jì)算屬性的聲明可以去掉get關(guān)鍵字和花括號。

  • 屬性觀察器監(jiān)控和響應(yīng)屬性值的變化,每次屬性被設(shè)置值的時(shí)候都會調(diào)用屬性觀察器,甚至新的值和現(xiàn)在的值相同的時(shí)候也不例外??梢詾槌搜舆t存儲屬性之外的其他存儲屬性添加屬性觀察器,也可以通過重寫屬性的方式為繼承的屬性(包括存儲屬性和計(jì)算屬性)添加屬性觀察器。屬性重寫請參考重寫。

注意:
不需要為非重寫的計(jì)算屬性添加屬性觀察器,因?yàn)榭梢酝ㄟ^它的 setter 直接監(jiān)控和響應(yīng)值的變化。

可以為屬性添加如下的一個(gè)或全部觀察器:
1.willSet在新的值被設(shè)置之前調(diào)用
2.didSet在新的值被設(shè)置之后立即調(diào)用
willSet觀察器會將新的屬性值作為常量參數(shù)傳入,在willSet的實(shí)現(xiàn)代碼中可以為這個(gè)參數(shù)指定一個(gè)名稱,如果不指定則參數(shù)仍然可用,這時(shí)使用默認(rèn)名稱newValue表示。類似地,didSet觀察器會將舊的屬性值作為參數(shù)傳入,可以為該參數(shù)命名或者使用默認(rèn)參數(shù)名oldValue

注意:
如果在一個(gè)屬性的didSet觀察器里為它賦值,這個(gè)值會替換該觀察器之前設(shè)置的值。

  • 類型屬性

使用關(guān)鍵字static來定義類型屬性。類型屬性用于定義特定類型所有實(shí)例共享的數(shù)據(jù),比如所有實(shí)例都能用的一個(gè)常量(就像 C 語言中的靜態(tài)常量),或者所有實(shí)例都能訪問的一個(gè)變量(就像 C 語言中的靜態(tài)變量)。
存儲型類型屬性可以是變量或常量,計(jì)算型類型屬性跟實(shí)例的計(jì)算型屬性一樣只能定義成變量屬性。跟實(shí)例的存儲型屬性不同,必須給存儲型類型屬性指定默認(rèn)值,因?yàn)轭愋捅旧頉]有構(gòu)造器,也就無法在初始化過程中使用構(gòu)造器給類型屬性賦值。


方法(Methods)

  • 實(shí)例方法

Swift 默認(rèn)僅給方法的第一個(gè)參數(shù)名稱一個(gè)局部參數(shù)名稱;默認(rèn)同時(shí)給第二個(gè)和后續(xù)的參數(shù)名稱局部參數(shù)名稱和外部參數(shù)名稱。如果你不想為方法的第二個(gè)及后續(xù)的參數(shù)提供一個(gè)外部名稱,可以通過使用下劃線(_)作為該參數(shù)的顯式外部名稱,這樣做將覆蓋默認(rèn)行為。
結(jié)構(gòu)體和枚舉是值類型。一般情況下,值類型的屬性不能在它的實(shí)例方法中被修改。但是,如果需要在某個(gè)具體的方法中修改結(jié)構(gòu)體或者枚舉的屬性,你可以選擇變異(mutating)這個(gè)方法,然后方法就可以從方法內(nèi)部改變它的屬性;并且它做的任何改變在方法結(jié)束時(shí)還會保留在原始結(jié)構(gòu)中。方法還可以給它隱含的self屬性賦值一個(gè)全新的實(shí)例,這個(gè)新實(shí)例在方法結(jié)束后將替換原來的實(shí)例。要使用變異方法, 將關(guān)鍵字mutating 放到方法的func關(guān)鍵字之前就可以了:
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
print("The point is now at ((somePoint.x), (somePoint.y))")
// 打印輸出: "The point is now at (3.0, 4.0)
可變方法能夠賦給隱含屬性self一個(gè)全新的實(shí)例。上面Point的例子可以用下面的方式改寫:
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}

  • 類型方法

實(shí)例方法是被類型的某個(gè)實(shí)例調(diào)用的方法。你也可以定義類型本身調(diào)用的方法,這種方法就叫做類型方法。聲明結(jié)構(gòu)體和枚舉的類型方法,在方法的func關(guān)鍵字之前加上關(guān)鍵字static。類可能會用關(guān)鍵字class來允許子類重寫父類的實(shí)現(xiàn)方法。


下標(biāo)腳本(Subscripts)

  • 下標(biāo)腳本語法

    subscript(index: Int) -> Int {
        get {
          // 返回與入?yún)⑵ヅ涞腎nt類型的值
      }
        set(newValue) {
          // 執(zhí)行賦值操作
        }
    }
    

newValue的類型必須和下標(biāo)腳本定義的返回類型相同。與計(jì)算型屬性相同的是set的入?yún)⒙暶?code>newValue就算不寫,在set代碼塊中依然可以使用默認(rèn)的newValue這個(gè)變量來訪問新賦的值。
與只讀計(jì)算型屬性一樣,可以直接將原本應(yīng)該寫在get代碼塊中的代碼寫在subscript中:

  subscript(index: Int) -> Int {
      // 返回與入?yún)⑵ヅ涞腎nt類型的值
  }

下面代碼演示了一個(gè)在TimesTable結(jié)構(gòu)體中使用只讀下標(biāo)腳本的用法,該結(jié)構(gòu)體用來展示傳入整數(shù)的n倍:
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("3的6倍是(threeTimesTable[6])")
// 輸出 "3的6倍是18”


繼承(Inheritance)

  • Swift 中的類并不是從一個(gè)通用的基類繼承而來。如果你不為你定義的類指定一個(gè)超類的話,這個(gè)類就自動成為基類。
  • 如果要重寫某個(gè)特性,你需要在重寫定義的前面加上override關(guān)鍵字。重寫一個(gè)屬性時(shí),必需將它的名字和類型都寫出來。不可以將一個(gè)繼承來的讀寫屬性重寫為一個(gè)只讀屬性。你可以將一個(gè)繼承來的只讀屬性重寫為一個(gè)讀寫屬性,只需要在重寫版本的屬性里提供 gettersetter 即可。但是,你不可以將一個(gè)繼承來的讀寫屬性重寫為一個(gè)只讀屬性。
    在合適的地方,你可以通過使用super前綴來訪問超類版本的方法,屬性或下標(biāo)腳本。

注意:
如果你在重寫屬性中提供了 setter,那么你也一定要提供 getter。如果你不想在重寫版本中的 getter 里修改繼承來的屬性值,你可以直接通過super.someProperty來返回繼承來的值,其中someProperty是你要重寫的屬性的名字
你不可以為繼承來的常量存儲型屬性或繼承來的只讀計(jì)算型屬性添加屬性觀察器。這些屬性的值是不可以被設(shè)置的,所以,為它們提供 willSetdidSet 實(shí)現(xiàn)是不恰當(dāng)。此外還要注意,你不可以同時(shí)提供重寫的 setter 和重寫的屬性觀察器。如果你想觀察屬性值的變化,并且你已經(jīng)為那個(gè)屬性提供了定制的 setter,那么你在 setter 中就可以觀察到任何值變化了。

  • 可以通過把方法,屬性或下標(biāo)腳本標(biāo)記為final防止它們被重寫,只需要在聲明關(guān)鍵字前加上final特性即可。(例如:final var, final func,final class func, 以及final subscript
    你可以通過在關(guān)鍵字class前添加final特性(final class)來將整個(gè)類標(biāo)記為 final 的,這樣的類是不可被繼承的。

構(gòu)造過程

  • 如果你在定義構(gòu)造器時(shí)沒有提供參數(shù)的外部名字,Swift 會為構(gòu)造器的每個(gè)參數(shù)自動生成一個(gè)跟內(nèi)部名字相同的外部名。只要構(gòu)造器定義了某個(gè)外部參數(shù)名,你就必須使用它,忽略它將導(dǎo)致編譯錯(cuò)誤。
    如果不希望為構(gòu)造器的某個(gè)參數(shù)提供外部名字,你可以使用下劃線(_)來顯示描述它的外部名。
  • 指定構(gòu)造器和便利構(gòu)造器

便利構(gòu)造器(convenience initializers)是類中比較次要的、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來調(diào)用同一個(gè)類中的指定構(gòu)造器,并為其參數(shù)提供默認(rèn)值。
指定構(gòu)造器必須總是向上代理,便利構(gòu)造器必須總是橫向代理。

  • 構(gòu)造器的繼承和重寫

當(dāng)你寫一個(gè)父類中帶有指定構(gòu)造器的子類構(gòu)造器時(shí),你需要重寫這個(gè)指定的構(gòu)造器。因此,你必須在定義子類構(gòu)造器時(shí)帶上override修飾符。
構(gòu)造器的自動繼承 子類在默認(rèn)情況下不會繼承父類的構(gòu)造器。但是如果滿足特定條件,父類構(gòu)造器是可以被自動繼承的。在實(shí)踐中,這意味著對于許多常見場景你不必重寫父類的構(gòu)造器,并且可以在安全的情況下以最小的代價(jià)繼承父類的構(gòu)造器。假設(shè)你為子類中引入的所有新屬性都提供了默認(rèn)值,以下 2 個(gè)規(guī)則適用:
規(guī)則 1:如果子類沒有定義任何指定構(gòu)造器,它將自動繼承所有父類的指定構(gòu)造器。
規(guī)則 2:如果子類提供了所有父類指定構(gòu)造器的實(shí)現(xiàn)——無論是通過規(guī)則 1 繼承過來的,還是提供了自定義實(shí)現(xiàn)——它將自動繼承所有父類的便利構(gòu)造器。即使你在子類中添加了更多的便利構(gòu)造器,這兩條規(guī)則仍然適用。

  • 如果一個(gè)類、結(jié)構(gòu)體或枚舉類型的對象,在構(gòu)造自身的過程中有可能失敗,則為其定義一個(gè)可失敗構(gòu)造器??梢栽谝粋€(gè)類,結(jié)構(gòu)體或是枚舉類型的定義中,添加一個(gè)或多個(gè)可失敗構(gòu)造器。其語法為在init關(guān)鍵字后面加添問號(init?)。

注意: 可失敗構(gòu)造器的參數(shù)名和參數(shù)類型,不能與其它非可失敗構(gòu)造器的參數(shù)名,及其類型相同。

可失敗構(gòu)造器,在構(gòu)建對象的過程中,創(chuàng)建一個(gè)其自身類型為可選類型的對象。通過return nil 語句,來表明可失敗構(gòu)造器在何種情況下“失敗”。
類的可失敗構(gòu)造器只能在所有的類屬性被初始化后和所有類之間的構(gòu)造器之間的代理調(diào)用發(fā)生完后觸發(fā)失敗行為。下面例子展示了如何使用隱式解析可選類型來實(shí)現(xiàn)這個(gè)類的可失敗構(gòu)造器的要求:
class Product {
let name: String!
init?(name: String) {
self.name = name
if name.isEmpty { return nil }
}
}
上面定義的Product類有一個(gè)不能為空字符串的name常量屬性。為了強(qiáng)制滿足這個(gè)要求,Product類使用了可失敗構(gòu)造器來確保這個(gè)屬性的值在構(gòu)造器成功時(shí)不為空。畢竟,Product是一個(gè)類而不是結(jié)構(gòu)體,Product類的所有可失敗構(gòu)造器必須在自己失敗前給name屬性一個(gè)初始值。上面的例子中,Product類的name屬性被定義為隱式解析可選字符串類型(String!),因?yàn)樗且粋€(gè)可選類型,所以在構(gòu)造過程里的賦值前,name屬性有個(gè)默認(rèn)值nil。用默認(rèn)值nil意味著Product類的所有屬性都有一個(gè)合法的初始值。因而,在構(gòu)造器中給name屬性賦一個(gè)特定的值前,可失敗構(gòu)造器能夠在傳入一個(gè)空字符串時(shí)觸發(fā)構(gòu)造過程的失敗。
可以用一個(gè)非可失敗構(gòu)造器重寫一個(gè)可失敗構(gòu)造器,但反過來卻行不通。

  • 必要構(gòu)造器

在類的構(gòu)造器前添加 required 修飾符表明所有該類的子類都必須實(shí)現(xiàn)該構(gòu)造器:
class SomeClass {
required init() {
// 在這里添加該必要構(gòu)造器的實(shí)現(xiàn)代碼
}
}
在子類重寫父類的必要構(gòu)造器時(shí),必須在子類的構(gòu)造器前也添加required修飾符,這是為了保證繼承鏈上子類的構(gòu)造器也是必要構(gòu)造器。在重寫父類的必要構(gòu)造器時(shí),不需要添加override修飾符。

  • 通過閉包和函數(shù)來設(shè)置屬性的默認(rèn)值

如果某個(gè)存儲型屬性的默認(rèn)值需要特別的定制或準(zhǔn)備,你就可以使用閉包或全局函數(shù)來為其屬性提供定制的默認(rèn)值。下面列舉了閉包如何提供默認(rèn)值的代碼概要:
class SomeClass {
let someProperty: SomeType = {
// 在這個(gè)閉包中給 someProperty 創(chuàng)建一個(gè)默認(rèn)值
// someValue 必須和 SomeType 類型相同
return someValue
}()
}
注意閉包結(jié)尾的大括號后面接了一對空的小括號。這是用來告訴 Swift 需要立刻執(zhí)行此閉包。如果你忽略了這對括號,相當(dāng)于是將閉包本身作為值賦值給了屬性,而不是將閉包的返回值賦值給屬性。

注意:
如果你使用閉包來初始化屬性的值,請記住在閉包執(zhí)行時(shí),實(shí)例的其它部分都還沒有初始化。這意味著你不能夠在閉包里訪問其它的屬性,就算這個(gè)屬性有默認(rèn)值也不允許。同樣,你也不能使用隱式的self屬性,或者調(diào)用其它的實(shí)例方法。


析構(gòu)過程(Reinitialization)

  • 析構(gòu)器只適用于類類型,當(dāng)一個(gè)類的實(shí)例被釋放之前,析構(gòu)器會被立即調(diào)用。析構(gòu)器用關(guān)鍵字deinit來標(biāo)示,類似于構(gòu)造器要用init來標(biāo)示。

自動引用計(jì)數(shù)(Automatic Reference Counting)

  • Swift 提供了兩種辦法用來解決在使用類的屬性時(shí)所遇到的循環(huán)強(qiáng)引用問題:弱引用(weak reference)和無主引用(unowned reference)。
  • 聲明屬性或者變量時(shí),在前面加上weak關(guān)鍵字表明這是一個(gè)弱引用。弱引用必須被聲明為變量,表明其值能在運(yùn)行時(shí)被修改。因?yàn)槿跻每梢詻]有值,你必須將每一個(gè)弱引用聲明為可選類型。在 Swift 中,推薦使用可選類型描述可能沒有值的類型。
  • 和弱引用類似,無主引用不會牢牢保持住引用的實(shí)例。和弱引用不同的是,無主引用是永遠(yuǎn)有值的。因此,無主引用總是被定義為非可選類型(non-optional type)。你可以在聲明屬性或者變量時(shí),在前面加上關(guān)鍵字unowned表示這是一個(gè)無主引用。使用無主引用,你必須確保引用始終指向一個(gè)未銷毀的實(shí)例。
  • 兩個(gè)屬性的值都允許為nil,并會潛在的產(chǎn)生循環(huán)強(qiáng)引用。這種場景最適合用弱引用來解決。
    一個(gè)屬性的值允許為nil,而另一個(gè)屬性的值不允許為nil,這也可能會產(chǎn)生循環(huán)強(qiáng)引用。這種場景最適合通過無主引用來解決。
    然而,存在著第三種場景,在這種場景中,兩個(gè)屬性都必須有值,并且初始化完成后永遠(yuǎn)不會為nil。在這種場景中,需要一個(gè)類使用無主屬性,而另外一個(gè)類使用隱式解析可選屬性。
  • 閉包引起的循環(huán)強(qiáng)引用

如果閉包有參數(shù)列表和返回類型,把捕獲列表放在它們前面:
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
如果閉包沒有指明參數(shù)列表或者返回類型,即它們會通過上下文推斷,那么可以把捕獲列表和關(guān)鍵字in放在閉包最開始的地方:
lazy var someClosure: Void -> String = {
[unowned self, weak delegate = self.delegate!] in
// closure body goes here
}


可空鏈?zhǔn)秸{(diào)用(Optional Chaining)

  • 可空鏈?zhǔn)秸{(diào)用(Optional Chaining)是一種可以請求和調(diào)用屬性、方法及下標(biāo)的過程,它的可空性體現(xiàn)于請求或調(diào)用的目標(biāo)當(dāng)前可能為空(nil)。如果可空的目標(biāo)有值,那么調(diào)用就會成功;如果選擇的目標(biāo)為空(nil),那么這種調(diào)用將返回空(nil)。多個(gè)連續(xù)的調(diào)用可以被鏈接在一起形成一個(gè)調(diào)用鏈,如果其中任何一個(gè)節(jié)點(diǎn)為空(nil)將導(dǎo)致整個(gè)鏈調(diào)用失敗。

注意: Swift 的可空鏈?zhǔn)秸{(diào)用和 Objective-C 中的消息為空有些相像,但是 Swift 可以使用在任意類型中,并且能夠檢查調(diào)用是否成功。

  • 使用可空鏈?zhǔn)秸{(diào)用來強(qiáng)制展開

通過在想調(diào)用非空的屬性、方法、或下標(biāo)的可空值(optional value)后面放一個(gè)問號,可以定義一個(gè)可空鏈。這一點(diǎn)很像在可空值后面放一個(gè)嘆號(?。﹣韽?qiáng)制展開其中值。它們的主要的區(qū)別在于當(dāng)可空值為空時(shí)可空鏈?zhǔn)街皇钦{(diào)用失敗,然而強(qiáng)制展開將會觸發(fā)運(yùn)行時(shí)錯(cuò)誤。


錯(cuò)誤處理

  • 表示并拋出錯(cuò)誤

在Swift中,錯(cuò)誤用遵循ErrorType協(xié)議類型的值來表示。這個(gè)空協(xié)議表示一種可以用做錯(cuò)誤處理的類型。 Swift的枚舉類型尤為適合塑造一組相關(guān)的錯(cuò)誤情形(error conditions),枚舉的關(guān)聯(lián)值(assiciated values)還可以提供額外信息,表示某些錯(cuò)誤情形的性質(zhì)。比如你可以這樣表示在一個(gè)游戲中操作自動販賣機(jī)會出現(xiàn)的錯(cuò)誤情形:
enum VendingMachineError: ErrorType {
case InvalidSelection //選擇無效
case InsufficientFunds(coinsNeeded: Int) //金額不足
case OutOfStock //缺貨
}

  • 處理錯(cuò)誤

某個(gè)錯(cuò)誤被拋出時(shí),那個(gè)地方的某部分代碼必須要負(fù)責(zé)處理這個(gè)錯(cuò)誤 - 比如糾正這個(gè)問題、嘗試另外一種方式、或是給用戶提示這個(gè)錯(cuò)誤。 Swift中有 4 種處理錯(cuò)誤的方式。你可以把函數(shù)拋出的錯(cuò)誤傳遞給調(diào)用此函數(shù)的代碼、用do-catch語句處理錯(cuò)誤、將錯(cuò)誤作為可選類型處理、或者斷言此錯(cuò)誤根本不會發(fā)生。

  • throws關(guān)鍵字標(biāo)來識一個(gè)可拋出錯(cuò)誤的函數(shù),方法或是構(gòu)造器。在函數(shù)聲明中的參數(shù)列表之后加上throws。一個(gè)標(biāo)識了throws的函數(shù)被稱作throwing函數(shù)。如果這個(gè)函數(shù)還有返回值類型,throws關(guān)鍵詞需要寫在箭頭(->)的前面:
    func canThrowErrors() throws -> String
    調(diào)用可拋出錯(cuò)誤函數(shù)時(shí)在它前面加上try關(guān)鍵字。
    func vend(itemNamed name: String) throws {
    guard let item = inventory[name] else {
    throw VendingMachineError.InvalidSelection
    }
    guard item.count > 0 else {
    throw VendingMachineError.OutOfStock
    }
    guard item.price <= coinsDeposited else {
    throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
    }
  • 可以使用一個(gè)do-catch語句運(yùn)行一段閉包代碼來做錯(cuò)誤處理。如果在do語句中的代碼拋出了一個(gè)錯(cuò)誤,則這個(gè)錯(cuò)誤會與catch語句做匹配來決定哪條語句能處理它。 下面是do-catch語句的通用形式:
    do {
    try expression
    statements
    } catch pattern 1 {
    statements
    } catch pattern 2 where condition {
    statements
    }
  • 將錯(cuò)誤作為可選類型處理

可以使用try?通過將其轉(zhuǎn)換成一個(gè)可選值來處理錯(cuò)誤。如果在評估try?表達(dá)式時(shí)一個(gè)錯(cuò)誤被拋出,那么這個(gè)表達(dá)式的值就是nil。例如下面代碼中的xy有相同的值和特性:
func someThrowingFunction() throws -> Int {
// ...
}

  let x = try? someThrowingFunction()

  let y: Int?
  do {
      y = try someThrowingFunction()
  } catch {
      y = nil
  }

如果someThrowingFunction()拋出一個(gè)錯(cuò)誤,xy的值是nil。否則xy的值就是該函數(shù)的返回值。

  • 指定清理操作

可以使用defer語句在代碼執(zhí)行到要離開當(dāng)前的代碼段之前去執(zhí)行一套語句。該語句讓你能夠做一些應(yīng)該被執(zhí)行的必要清理工作。defer 語句將代碼的執(zhí)行延遲到當(dāng)前的作用域退出之前。該語句由 defer 關(guān)鍵字和要被延遲執(zhí)行的語句組成。延遲執(zhí)行的語句不能包含任何控制轉(zhuǎn)移語句,例如break 或是 return 語句,或是拋出一個(gè)錯(cuò)誤。延遲執(zhí)行的操作會按照它們被指定時(shí)的順序的相反順序執(zhí)行——也就是說,第一條 defer 語句中的代碼會在第二條defer 語句中的代碼被執(zhí)行之后才執(zhí)行,以此類推。
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 處理文件。
}
// close(file) 會在這里被調(diào)用,即作用域的最后。
}
}
上面的代碼使用一條defer 語句來確保 open(_:)函數(shù)有一個(gè)相應(yīng)的對 close(_:) 函數(shù)的調(diào)用。

:注意:
即使沒有涉及到錯(cuò)誤處理,你也可以使用 defer 語句。


類型轉(zhuǎn)換(Type Casting)

  • 類型檢查操作符(is)來檢查一個(gè)實(shí)例是否屬于特定子類型。若實(shí)例屬于那個(gè)子類型,類型檢查操作符返回true,否則返回false。
  • 向下轉(zhuǎn)型

某類型的一個(gè)常量或變量可能在幕后實(shí)際上屬于一個(gè)子類。當(dāng)確定是這種情況時(shí),你可以嘗試向下轉(zhuǎn)到它的子類型,用類型轉(zhuǎn)換操作符(as?as!)
當(dāng)你不確定向下轉(zhuǎn)型可以成功時(shí),用類型轉(zhuǎn)換的條件形式(as?)。條件形式的類型轉(zhuǎn)換總是返回一個(gè)可選值(optional value),并且若下轉(zhuǎn)是不可能的,可選值將是 nil。這使你能夠檢查向下轉(zhuǎn)型是否成功。
只有你可以確定向下轉(zhuǎn)型一定會成功時(shí),才使用強(qiáng)制形式(as!)。當(dāng)你試圖向下轉(zhuǎn)型為一個(gè)不正確的類型時(shí),強(qiáng)制形式的類型轉(zhuǎn)換會觸發(fā)一個(gè)運(yùn)
行時(shí)錯(cuò)誤。

  • AnyObject可以代表任何class類型的實(shí)例。
    Any可以表示任何類型,包括方法類型(function types)。

擴(kuò)展(Extensions)

  • 擴(kuò)展語法

聲明一個(gè)擴(kuò)展使用關(guān)鍵字extension
extension SomeType {
// 加到SomeType的新功能寫到這里
}
一個(gè)擴(kuò)展可以擴(kuò)展一個(gè)已有類型,使其能夠適配一個(gè)或多個(gè)協(xié)議(protocol)。當(dāng)這種情況發(fā)生時(shí),協(xié)議的名字應(yīng)該完全按照類或結(jié)構(gòu)體的名字的方式進(jìn)行書寫:
extension SomeType: SomeProtocol, AnotherProctocol {
// 協(xié)議實(shí)現(xiàn)寫到這里
}

  • 擴(kuò)展可以向已有類型添加計(jì)算型實(shí)例屬性和計(jì)算型類型屬性。
  • 擴(kuò)展可以向已有類型添加新的構(gòu)造器。這可以讓你擴(kuò)展其它類型,將你自己的定制類型作為構(gòu)造器參數(shù),或者提供該類型的原始實(shí)現(xiàn)中沒有包含的額外初始化選項(xiàng)。
    擴(kuò)展能向類中添加新的便利構(gòu)造器,但是它們不能向類中添加新的指定構(gòu)造器或析構(gòu)器。指定構(gòu)造器和析構(gòu)器必須總是由原始的類實(shí)現(xiàn)來提供。
  • 擴(kuò)展可以向已有類型添加新的實(shí)例方法和類型方法。
  • 擴(kuò)展可以向一個(gè)已有類型添加新下標(biāo)。
  • 擴(kuò)展可以向已有的類、結(jié)構(gòu)體和枚舉添加新的嵌套類型。

協(xié)議(Protocols)

  • 協(xié)議的語法

協(xié)議的定義方式與類,結(jié)構(gòu)體,枚舉的定義非常相似:
protocol SomeProtocol {
// 協(xié)議內(nèi)容
}
要使類遵循某個(gè)協(xié)議,需要在類型名稱后加上協(xié)議名稱,中間以冒號:分隔,作為類型定義的一部分。遵循多個(gè)協(xié)議時(shí),各協(xié)議之間用逗號,分隔:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 結(jié)構(gòu)體內(nèi)容
}
如果類在遵循協(xié)議的同時(shí)擁有父類,應(yīng)該將父類名放在協(xié)議名之前,以逗號分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 類的內(nèi)容
}

  • 協(xié)議中的通常用var來聲明變量屬性,在類型聲明后加上{ set get }來表示屬性是可讀可寫的,只讀屬性則用{ get }來表示。
    在協(xié)議中定義類屬性(type property)時(shí),總是使用static關(guān)鍵字作為前綴。當(dāng)協(xié)議的遵循者是類時(shí),可以使用classstatic關(guān)鍵字來聲明類屬性。
  • 在協(xié)議中定義類方法的時(shí)候,總是使用static關(guān)鍵字作為前綴。當(dāng)協(xié)議的遵循者是類的時(shí)候,你可以在類的實(shí)現(xiàn)中使用class或者static來實(shí)現(xiàn)類方法。
  • 有時(shí)需要在方法中改變它的實(shí)例。例如,值類型(結(jié)構(gòu)體,枚舉)的實(shí)例方法中,將mutating關(guān)鍵字作為函數(shù)的前綴,寫在func之前,表示可以在該方法中修改它所屬的實(shí)例及其實(shí)例屬性的值。
  • 協(xié)議可以要求它的遵循者實(shí)現(xiàn)指定的構(gòu)造器。你可以像書寫普通的構(gòu)造器那樣,在協(xié)議的定義里寫下構(gòu)造器的聲明,但不需要寫花括號和構(gòu)造器的實(shí)體:
    protocol SomeProtocol {
    init(someParameter: Int)
    }
    如果一個(gè)子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器遵循了某個(gè)協(xié)議的規(guī)定,那么該構(gòu)造器的實(shí)現(xiàn)需要被同時(shí)標(biāo)示requiredoverride修飾符。
  • 通過擴(kuò)展補(bǔ)充協(xié)議聲明

當(dāng)一個(gè)類型已經(jīng)實(shí)現(xiàn)了協(xié)議中的所有要求,卻沒有聲明為遵循該協(xié)議時(shí),可以通過擴(kuò)展(空的擴(kuò)展體)來補(bǔ)充協(xié)議聲明:
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named (name)"
}
}
extension Hamster: TextRepresentable {}
從現(xiàn)在起,Hamster的實(shí)例可以作為TextRepresentable類型使用:
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// 輸出 "A hamster named Simon

  • 類專屬協(xié)議

你可以在協(xié)議的繼承列表中,通過添加class關(guān)鍵字,限制協(xié)議只能適配到類(class)類型。(結(jié)構(gòu)體或枚舉不能遵循該協(xié)議)。該class關(guān)鍵字必須是第一個(gè)出現(xiàn)在協(xié)議的繼承列表中,其后,才是其他繼承協(xié)議。
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 協(xié)議定義
}
在以上例子中,協(xié)議SomeClassOnlyProtocol只能被類(class)類型適配。如果嘗試讓結(jié)構(gòu)體或枚舉類型適配該協(xié)議,則會出現(xiàn)編譯錯(cuò)誤。

  • 檢驗(yàn)協(xié)議的一致性

你可以使用is和as操作符來檢查是否遵循某一協(xié)議或強(qiáng)制轉(zhuǎn)化為某一類型。檢查和轉(zhuǎn)化的語法和之前相同(詳情查看類型轉(zhuǎn)換):
is操作符用來檢查實(shí)例是否遵循了某個(gè)協(xié)議。
as?返回一個(gè)可選值,當(dāng)實(shí)例遵循協(xié)議時(shí),返回該協(xié)議類型;否則返回nil。
as用以強(qiáng)制向下轉(zhuǎn)型,如果強(qiáng)轉(zhuǎn)失敗,會引起運(yùn)行時(shí)錯(cuò)誤。

  • 可選協(xié)議只能在含有@objc前綴的協(xié)議中生效。 這個(gè)前綴表示協(xié)議將暴露給Objective-C代碼,詳情參見Using Swift with Cocoa and Objective-C(Swift 2.1)。即使你不打算和Objective-C有什么交互,如果你想要指明協(xié)議包含可選屬性,那么還是要加上@obj前綴。 還需要注意的是,@objc的協(xié)議只能由繼承自 Objective-C 類的類或者其他的@objc類來遵循。它也不能被結(jié)構(gòu)體和枚舉遵循。
    下面的例子定義了一個(gè)叫Counter的整數(shù)加法類,它使用外部的數(shù)據(jù)源來提供每次的增量。數(shù)據(jù)源是兩個(gè)可選規(guī)定,在CounterDataSource協(xié)議中定義:
    @objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int
    optional var fixedIncrement: Int { get }
    }
  • 為協(xié)議擴(kuò)展添加限制條件

在擴(kuò)展協(xié)議的時(shí)候,可以指定一些限制,只有滿足這些限制的協(xié)議遵循者,才能獲得協(xié)議擴(kuò)展提供的屬性和方法。這些限制寫在協(xié)議名之后,使用where關(guān)鍵字來描述限制情況。(Where語句)。:
例如,你可以擴(kuò)展CollectionType協(xié)議,但是只適用于元素遵循TextRepresentable的情況:
extension CollectionType where Generator.Element : TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joinWithSeparator(", ") + "]"
}
}


泛型(Generics)

  • 泛型代碼可以讓你寫出根據(jù)自我需求定義、適用于任何類型的,靈活且可重用的函數(shù)和類型。它的可以讓你避免重復(fù)的代碼,用一種清晰和抽象的方式來表達(dá)代碼的意圖。
  • 泛型函數(shù)可以工作于任何類型,這里用于交換兩個(gè)值:
    func swapTwoValues<T>(inout a: T, inout _ b: T) {
    let temporaryA = a
    a = b
    b = temporaryA
    }
    //只交換int
    func swapTwoInts(inout a: Int, inout _ b: Int)
    //交換相同的類型
    func swapTwoValues<T>(inout a: T, inout _ b: T)
    這個(gè)函數(shù)的泛型版本使用了占位類型名字(通常此情況下用字母T來表示)來代替實(shí)際類型名(如Int、String或Double)。
  • 當(dāng)你擴(kuò)展一個(gè)泛型類型的時(shí)候,你并不需要在擴(kuò)展的定義中提供類型參數(shù)列表(例如<T>)。更加方便的是,原始類型定義中聲明的類型參數(shù)列表在擴(kuò)展里是可以使用的。
  • 類型約束

    func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
        // 這里是函數(shù)主體
    }
    

上面這個(gè)假定函數(shù)有兩個(gè)類型參數(shù)。第一個(gè)類型參數(shù)T,有一個(gè)需要T必須是SomeClass子類的類型約束;第二個(gè)類型參數(shù)U,有一個(gè)需要U必須遵循SomeProtocol協(xié)議的類型約束。

  • 關(guān)聯(lián)類型(Associated Types)

當(dāng)定義一個(gè)協(xié)議時(shí),有的時(shí)候聲明一個(gè)或多個(gè)關(guān)聯(lián)類型作為協(xié)議定義的一部分是非常有用的。一個(gè)關(guān)聯(lián)類型作為協(xié)議的一部分,給定了類型的一個(gè)占位名(或別名)。作用于關(guān)聯(lián)類型上實(shí)際類型在協(xié)議被實(shí)現(xiàn)前是不需要指定的。關(guān)聯(lián)類型被指定為typealias關(guān)鍵字。


訪問控制(Access Control)

  • 訪問控制可以限定其他源文件或模塊中代碼對你代碼的訪問級別。這個(gè)特性可以讓我們隱藏功能實(shí)現(xiàn)的一些細(xì)節(jié),并且可以明確的申明我們提供給其他人的接口中哪些部分是他們可以訪問和使用的。
  • 訪問級別

“Swift 為代碼中的實(shí)體提供了三種不同的訪問級別。這些訪問級別不僅與源文件中定義的實(shí)體相關(guān),同時(shí)也與源文件所屬的模塊相關(guān)。
public:可以訪問自己模塊中源文件里的任何實(shí)體,別人也可以通過引入該模塊來訪問源文件里的所有實(shí)體。通常情況下,Framework 中的某個(gè)接口是可以被任何人使用時(shí),你可以將其設(shè)置為public級別。
internal:可以訪問自己模塊中源文件里的任何實(shí)體,但是別人不能訪問該模塊中源文件里的實(shí)體。通常情況下,某個(gè)接口或Framework作為內(nèi)部結(jié)構(gòu)使用時(shí),你可以將其設(shè)置為internal級別。
private:只能在當(dāng)前源文件中使用的實(shí)體,稱為私有實(shí)體。使用private級別,可以用作隱藏某些功能的實(shí)現(xiàn)細(xì)節(jié)。
除非有特殊的說明,否則實(shí)體都使用默認(rèn)的訪問級別internal
public class SomePublicClass { // 顯式的 public 類
public var somePublicProperty = 0 // 顯式的 public 類成員
var someInternalProperty = 0 // 隱式的 internal 類成員
private func somePrivateMethod() {} // 顯式的 private 類成員
}

  class SomeInternalClass {               // 隱式的 internal 類
         var someInternalProperty = 0         // 隱式的 internal 類成員
         private func somePrivateMethod() {}  // 顯式的 private 類成員
  }

  private class SomePrivateClass {        // 顯式的 private 類
         var somePrivateProperty = 0          // 隱式的 private 類成員
         func somePrivateMethod() {}          // 隱式的 private 類成員
  }
  • 元組的訪問級別與元組中訪問級別最低的類型一致。

  • 子類的訪問級別不得高于父類的訪問級別。如果我們無法直接訪問某個(gè)類中的屬性或函數(shù)等,那么可以繼承該類,從而可以更容易的訪問到該類的類成員:
    public class A {
    private func someMethod() {}
    }

    internal class B: A {
           override internal func someMethod() {}
    }
    
  • 常量、變量、屬性不能擁有比它們的類型更高的訪問級別。
    元組的訪問級別與元組中訪問級別最低的類型一致 。

  • 如果想為一個(gè)協(xié)議明確的申明訪問級別,那么需要注意一點(diǎn),就是你要確保該協(xié)議只在你申明的訪問級別作用域中使用。
    如果定義了一個(gè)新的協(xié)議,并且該協(xié)議繼承了一個(gè)已知的協(xié)議,那么新協(xié)議擁有的訪問級別最高也只和被繼承協(xié)議的訪問級別相同。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、范型范型所解決的問題 函數(shù)、方法、類型:類,結(jié)構(gòu)體,枚舉,元組類型,協(xié)議參數(shù),返回值,成員函數(shù)參數(shù),成員屬性類...
    我是小胡胡123閱讀 956評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評論 19 139
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 4,202評論 1 10
  • 132.轉(zhuǎn)換錯(cuò)誤成可選值 通過轉(zhuǎn)換錯(cuò)誤成一個(gè)可選值,你可以使用 try? 來處理錯(cuò)誤。當(dāng)執(zhí)行try?表達(dá)式時(shí),如果...
    無灃閱讀 1,466評論 0 3
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 12,523評論 6 13

友情鏈接更多精彩內(nèi)容