The Swift Programming Language 重讀筆記

前言

SwiftGG 翻譯組的 《The Swift Programming Language》in Chinese 我在 Swift3 的時候通讀過一遍,在 Swift4 的時候只瀏覽了前半部分笔咽,并未通讀±叮現(xiàn)在 Swift5 準備再重新讀一遍文檔,把一些遺忘的馆衔、易忘的椒楣、感覺值得記下的知識點梳理一遍,以方便記憶菩浙。

基礎部分

  1. 一般來說,很少需要寫類型注釋(type animation)句伶,如果在聲明常量或者變量時賦了一個初始值劲蜻, Swift 可以推斷出這個常量或變量的類型。

  2. 如果需要使用 Swift 保留關鍵字相同的名稱作為常量或變量名考余,可以使用反引號將關鍵字包圍的方式將其作為名字使用先嬉。無論如何,我們都應當避免使用關鍵字作為常量或變量名楚堤,除非別無選擇疫蔓。

  3. 當遇到一些相關值得簡單分組時,元組是很有用的身冬。元組不適合用來創(chuàng)建復雜的數(shù)據(jù)結構衅胀。如果數(shù)據(jù)結構比較復雜,不要使用元組酥筝,用類或者結構體去建模滚躯。

  4. ifguard 用戶區(qū)別:在 if 條件語句中使用常量或變量來創(chuàng)建一個可選綁定,僅在 if 語句的句中 body 中才能獲取到值。在 guard 語句中使用常量或變量創(chuàng)建一個可選綁定時掸掏,僅在 guard 語句外且在語句后才能獲取到值茁影。

  5. 斷言和先決條件的不同點是,他們什么時候進行狀態(tài)檢測:斷言僅在調(diào)試環(huán)境運行丧凤,而先決條件則在調(diào)試環(huán)境和生產(chǎn)環(huán)境中運行呼胚。在生產(chǎn)環(huán)境中,斷言的條件將不進行評估息裸。這意味著我們可以再開發(fā)階段使用很多斷言蝇更,但是這些斷言再生產(chǎn)環(huán)境都不會產(chǎn)生影響。

基本運算符

  1. 在對負數(shù) b 求余時呼盆,b 的符號會被忽略年扩。這意味著 a % -ba % b 是一樣的結果。但是 -a % ba % b 結果并不一樣访圃。

  2. 一元正負號 + 不做任何改變的返回操作數(shù)的值厨幻。 雖然一元操作符什么都不會改變,但當使用一元負號 - 來表達負數(shù)時腿时,可以使用一元正好來表達正數(shù)况脆,使代嗎具有對稱美。

     let minusSix = -6
     let alsoMinusSix = +minusSix  // alsoMinusSix 等于 -6
    
  3. 如果兩個元組的元素相同批糟,且長度相同的話格了,元組就可以被比較,比較元組大小會按照從左到右徽鼎、逐值比較的方式盛末,直到發(fā)現(xiàn)兩個值不相等時停止,如果所有值都相等否淤,那么我們就稱這一對元組是相等的悄但。Swift 標準庫只能比較七個以內(nèi)元素的元組。如果元組元素超過七個石抡,則需要自己實現(xiàn)比較運算符檐嚣。

    (1, "zebra") < (2, "apple")   // true,因為 1 小于 2
    (3, "apple") < (3, "bird")    // true啰扛,因為 3 等于 3嚎京,但是apple 小于 bird
    (4, "dog") == (4, "dog")      // true,因為 4 等于 4侠讯,dog 等于 dog
    
  1. 空合運算符 ??挖藏。ps. 其實我一直我不清除這個叫啥名字。厢漩。

字符串和字符

  1. 多行字符串字面量,是由一對三個雙引號包裹著的具有固定順序的文本字符集岩臣。也可以在行尾寫一個反斜杠 \ 作為續(xù)行符溜嗜。

      let softWrappedQuotation = """
         The White Rabbit put on his spectacles.  "Where shall I begin, \
         please your Majesty?" he asked.
    
         "Begin at the beginning," the King said gravely, "and go on \
         till you come to the end; then stop."
         """
    
  1. 關于 字符串字面量的特殊字符,主要是轉義字符、Unicode 標量先鱼。平時使用較少君纫。還有轉義字符反斜杠 \ 的使用。
  1. 擴展字符串分隔符 #土全。將字符串文字放在擴展分隔符中捎琐,這樣字符串中的特殊字符將會被直接包含而不是轉移后的效果。如果想要轉義字符效果裹匙,用等量 # 包裹住即可瑞凑。

  2. 字符串、結構體概页、枚舉籽御,都是值類型。

  3. 字符串知識中惰匙,關于 Unicode 技掏,比較零碎,實際開發(fā)中也很少用到项鬼,已讀哑梳。

  4. 關于計算字符數(shù)量,需要注意的是通過 count 屬性返回的字符數(shù)量并不總是與包含相同自負的 NSStringlength 屬性相同绘盟。 NSStringlength 屬性是利用 UTF-16 表示的十六位代碼單元數(shù)字涧衙,而不是 Unicode 可擴展的字符群集。

  5. 不同的字符可能會占用不同數(shù)量的存儲空間奥此,要以要知道字符串中 Character 的確定位置弧哎,就必須從 String 開頭遍歷每一個 Unicode 標量到結尾。因此稚虎, Swift 的字符串不能整數(shù) integer 做索引撤嫩。

  6. 子字符串 SubStringString 區(qū)別在于性能優(yōu)化上。SubString 可以重用原 String 的內(nèi)存空間蠢终,或者另一個 SubString 的內(nèi)存空間(String 也有同樣的優(yōu)化序攘,但如果兩個 String 共享內(nèi)存的話,它們就會相等)寻拂。這一優(yōu)化意味著你在修改 StringSubString 之前都不需要消耗性能去復制內(nèi)存程奠。就像前面說的那樣,SubString 不適合長期存儲 —— 因為它重用了原 String 的內(nèi)存空間祭钉,原 String 的內(nèi)存空間必須保留直到它的 SubString 不再被使用為止瞄沙。

集合類型

  1. 關于數(shù)組,每天都在用,要強調(diào)的不多距境。這里說三個方法及區(qū)別:removeLast() 申尼,移除數(shù)組最后一個元素并返回該元素,數(shù)組必須不為空垫桂,否則會引發(fā)運行時崩潰师幕; popLast() ,移除數(shù)組最后一個可選類型的元素诬滩,也就是說霹粥,數(shù)組可以為空; dropLast() 疼鸟,移除掉最后幾個元素后控,并返回移除后的原數(shù)組。

    var digitArr = [1,2,3,4,5]
    var last1 = digitArr.removeLast()
    var last2 = digitArr.popLast()
    var remain = digitArr.dropLast(2)
    print(last1, last2 ?? "-2", remain)
    // 輸出: 5 4 [1]
    
  2. 集合 Set愚臀。用來存儲相同類型并且沒有順序的值忆蚀。當集合元素順序不重要并且確保每個元素只出現(xiàn)一次時,可以使用集合而不是數(shù)組姑裂〔鐾啵可自己定義類型作為集合的值類型,但是定義的類型要遵循 Hasshable 協(xié)議舶斧,而 Hashable 協(xié)議遵循了 Equatable 協(xié)議欣鳖,所以定義的類型符合這些協(xié)議即可。

  3. 集合提供了一些方法可以高效完成對集合的操作茴厉。

    • 使用 intersection(_:) 方法根據(jù)兩個集合的交集創(chuàng)建一個新的集合泽台。
    • 使用 symmetricDifference(_:) 方法根據(jù)兩個集合不相交的值創(chuàng)建一個新的集合。
    • 使用 union(_:) 方法根據(jù)兩個集合的所有值創(chuàng)建一個新的集合矾缓。
    • 使用 subtracting(_:) 方法根據(jù)不在另一個集合中的值創(chuàng)建一個新的集合怀酷。
  4. 集合還提供了方法來判斷集合之間的關系:

    • 使用“是否相等”運算符 == 來判斷兩個集合包含的值是否全部相同。
    • 使用 isSubset(of:) 方法來判斷一個集合中的所有值是否也被包含在另外一個集合中嗜闻。
    • 使用 isSuperset(of:) 方法來判斷一個集合是否包含另一個集合中所有的值蜕依。
    • 使用 isStrictSubset(of:) 或者 isStrictSuperset(of:) 方法來判斷一個集合是否是另外一個集合的子集合或者父集合并且兩個集合并不相等。
    • 使用 isDisjoint(with:) 方法來判斷兩個集合是否不含有相同的值(是否沒有交集)琉雳。
  1. 字典 Dictionary 是一個無序集合样眠,存儲鍵值之間的關系。為了以特定的順序遍歷字典的鍵或值翠肘,可以對字典的 keysvalues 使用 sorted() 方法檐束。如果想要移除字典中的相關元素,可以可以使用如下兩種方式:

    var ages = ["Tom": 20, "jack": 12, "Lilei": 18]
    ages.removeValue(forKey: "Tom")
    print(ages)
    /// ["Lilei": 18, "jack": 12]
    ages["KK"] = nil
    print(ages)
    /// ["Lilei": 18, "jack": 12]
    ages["jack"] = nil
    print(ages)
    /// ["Lilei": 18]
    

控制流

  1. Swift 提供了多種流程控制結構束倍,包括可以多次執(zhí)行任務的 While 循環(huán)被丧,基于特定條件選擇執(zhí)行不同代碼分支的 if 盟戏、 guardSwitch 語句晚碾,還有控制流程跳轉到其他位置的 breakcontinue 語句抓半。

    Swift 還提供了 for-in 循環(huán)喂急,用來更簡單的遍歷數(shù)組格嘁、字典、區(qū)間廊移、字符串和其他序列類型糕簿。

  2. for-in 循環(huán)中,值得注意的有一點: stride(from:to:by:) 用于半開半閉區(qū)間狡孔, stride(from:through:by:) 用于閉區(qū)間懂诗,都是間隔循環(huán)。示例如下:

    let minutes = 15
    let minuteInterval = 5
    for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
        print("tickMark = ", tickMark)
    }
    /// 輸出: 0  5  10
    for tickMark in stride(from: 0, through: minutes, by: minuteInterval) {
        print("tickMark2 = ", tickMark)
    }
    /// 輸出: 0  5  10  15
    
  3. while 循環(huán)從計算一個條件開始苗膝,如果條件為 true 殃恒,會重復運行一段代碼,直到條件變?yōu)?false辱揭。repeat-whilewhile 的區(qū)別在于判斷循環(huán)條件之前离唐,先執(zhí)行一次循環(huán)的代碼塊。然后重復循環(huán)直到條件為 false问窃。

  4. SwitchSwift 中是非常強大亥鬓、靈活的。支持區(qū)間匹配域庇、元組匹配嵌戈、值綁定、where 條件听皿、復合型 case 以及包含相同值綁定的復合型 case 等熟呛。

  5. Swift 共有五種控制轉移語句:
    continuebreak``fallthrough尉姨,return庵朝,throw

    • continue :告訴一個循環(huán)立即停止本次循環(huán)啊送,重新開始下一次的循環(huán)偿短。但是不會離開整個循環(huán)。

    • break:立刻結束整個控制流的執(zhí)行馋没。 break 在循環(huán)中昔逗,是結束該循環(huán)。在 Switch 中篷朵,是結束該 Switch勾怒。

    • fallthrough:在 switch 中使用婆排,該關鍵字不會檢查它下一個將會落入執(zhí)行的 case 中的匹配條件。fallthrough 簡單地使代碼繼續(xù)連接到下一個 case 中的代碼笔链。這和 C 語言標準中的 switch 特性是一樣的段只。

  1. 帶標簽的語句。這個我個人確實沒有用到過鉴扫。這里記一下赞枕。簡單來說,就是給語句命名坪创,然后控制轉移語句直接針對特性語句做處理炕婶,下面是代碼:

    let finalSquare = 25
    var board = [Int](repeating: 0, count: finalSquare + 1)
    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 {
        diceRoll += 1
        if diceRoll == 7 { diceRoll = 1 }
        switch square + diceRoll {
        case finalSquare:
            // 骰子數(shù)剛好使玩家移動到最終的方格里,游戲結束莱预。
            break gameLoop
        case let newSquare where newSquare > finalSquare:
            // 骰子數(shù)將會使玩家的移動超出最后的方格柠掂,那么這種移動是不合法的,玩家需要重新擲骰子
            continue gameLoop
        default:
            // 合法移動依沮,做正常的處理
            square += diceRoll
            square += board[square]
        }
    }
    print("Game over!")
    
  2. 提前退出語句 guard 涯贞。

  3. 檢測 API 可用性,只要是針對不同系統(tǒng)版本的兼容危喉。通用寫法:

    if #available(平臺名稱 版本號, ..., *) {
        APIs 可用宋渔,語句將執(zhí)行
    } else {
        APIs 不可用,語句將不執(zhí)行
    }
    

    實際案例展示:

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

函數(shù)

  1. 函數(shù)的可變參數(shù)姥饰。一個可變參數(shù) variadic parameter 可以接受零個或多個值傻谁。通過在變量類型名后面加入 ... 的方式來定義可變參數(shù)。傳入值在函數(shù)體中變?yōu)榇祟愋偷囊粋€數(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)
    // 返回 3.0, 是這 5 個數(shù)的平均數(shù)。
    arithmeticMean(3, 8.25, 18.75)
    // 返回 10.0, 是這 3 個數(shù)的平均數(shù)岂座。
    
  2. 定義輸入輸出函數(shù)态蒂,在參數(shù)定義前加 inout 關鍵字即可。注意费什,只能傳變量給輸入輸出參數(shù)钾恢,不能傳常量或字面量,因為這些量是不能被修改的鸳址。輸入輸出參數(shù)不能有默認值瘩蚪,而且可變參數(shù)不能使用 inout 標記。

閉包

  1. 關于閉包表達式的進化稿黍。雖然越變越簡單疹瘦,但是,還是應該以清晰明了為主巡球。示例只是對閉包語法的一個說明:

    /// 排序數(shù)組
    let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
    /// 1.0 版本
    func backward(_ s1: String, _ s2: String) -> Bool {
        return s1 > s2
    }
    var reversedNames = names.sorted(by: backward)
    print(reversedNames)
    // reversedNames 為 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
    
    /// 2.0 版本
    reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
        return s1 > s2
    })
    
    /// 3.0 版本
    reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
    
    /// 4.0 版本
    reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
    
    /// 5.0 版本
    reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
    
    /// 6.0 版本
    reversedNames = names.sorted(by: { $0 > $1 } )
    
    /// 最終版
    reversedNames = names.sorted(by: >)
    
  2. 閉包是引用類型Q糟濉邓嘹!

  3. 在逃逸閉包內(nèi)必須顯示的調(diào)用 self ,非逃逸閉包則必須要险胰。所謂逃逸閉包汹押,就是指函數(shù)返回后才會執(zhí)行的閉包。典型的就是網(wǎng)絡閉包起便,在請求結束后才會執(zhí)行棚贾。還有自動閉包 autoclosure ,但是這個并不建議使用過多缨睡。

    override func viewDidLoad() {
        super.viewDidLoad()
        
        someFunctionWithNonescapingClosure {
            x = 50
        }
        someFunctionWithEscapingClosure {
            self.x = 100
        }
        someFuncationWithAutoclosureClosure(closure: "Hello")
    }
    
    /// 非逃逸閉包
    func someFunctionWithNonescapingClosure(closure: () -> Void) {
         closure()
    }
    /// 逃逸閉包
    func someFunctionWithEscapingClosure(closure: @escaping () -> Void) {
        closure()
    }
    /// 自動轉化閉包
    func someFuncationWithAutoclosureClosure(closure: @autoclosure () -> String) {
        print("\(closure())")
    }
    

枚舉

  1. 令枚舉遵循 CaseIterable 協(xié)議鸟悴。Swift 會生成一個 allCases 屬性陈辱,用于一個包含枚舉所有成員的集合奖年。如:

    enum Beverage: CaseIterable {
        case coffee, tea, juice
    }
    let numberOfChoices = Beverage.allCases.count
    print("\(numberOfChoices) beverages available")
    // 打印“3 beverages available”
    
  2. 遞歸枚舉是一種枚舉類型,它由一個或多個枚舉成員使用該枚舉類型的實例作為關聯(lián)值沛贪。用遞歸枚舉時陋守,編譯器會插入一個間接層±常可以在枚舉成員前加上 indirect 來標識該成員可遞歸水评。

    例如,下面的例子媚送,枚舉存儲了簡單的算術表達式:

    enum ArithmeticExpression {
        case number(Int)
        indirect case addition(ArithmeticExpression, ArithmeticExpression)
        indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
    }
    

    也可以再枚舉類型開頭加上 indirect 關鍵字來表明它的所有成員都是可遞歸的:

    indirect enum ArithmeticExpression {
        case number(Int)
        case addition(ArithmeticExpression, ArithmeticExpression)
        case multiplication(ArithmeticExpression, ArithmeticExpression)
    }
    

類和結構體

  1. Swift 中機構體和類有很多共同點中燥,兩者都可以:

    • 定義屬性有存儲值
    • 定義方法用于提供功能
    • 定義下標操作用于通過下標訪問它們的值
    • 定義構造器用于生成初始值
    • 通過擴展增加默認實現(xiàn)以外的功能
    • 遵循協(xié)議提供某些標準功能

    與結構體相比,類還有如下的附加功能:

    • 繼承塘偎,允許一個類繼承另一個類的特征
    • 類型轉換疗涉,允許在運行時檢查和解釋一個類實例的類型
    • 析構器,允許一個類實例釋放任務其所被分配的資源
    • 引用計數(shù)吟秩,允許對一個類多次引用

    類增加的附加功能是以增加復雜性為代價的咱扣,作為一般準則,優(yōu)先使用結構體涵防,因為它們更容易理解闹伪,僅在適當或必要時使用類。實際上壮池,這意味著我們大多數(shù)自定義的數(shù)據(jù)類型都應該是結構體和枚舉偏瓤。

  2. 恒等運算符

    因為類是引用類型,所以多個常量或變量幕后可能引用同一個類實例椰憋。對于結構體和枚舉來說厅克,這并不成立,因為他們都是值類型熏矿,再被賦予到常量已骇、變量或者傳遞到函數(shù)時离钝,其值總是會被拷貝。

    判定兩個常量或者變量是否引用同一個類實例又是會非常有用褪储。為了達到這個目的卵渴,Swift 提供了兩個恒等運算符,使用這兩個運算符檢測兩個常量或者變量是否引用同一個類實例:

    • 相同 ===
    • 不相同 !==

    請注意鲤竹,相同 (用三個等號標識 ===) 與 相等 (用兩個等號表示 ==) 的不同浪读,相同 表示兩個類類型(class type) 的常量或變量是否引用同一個類實例,相等 用于表示兩個值是否相等或等價辛藻,判定時要遵循設計者定義的評判標準碘橘。

屬性

  1. 全局的常量或者變量都是延遲計算的,跟延時加載存儲屬性相似吱肌,不同的地方在于痘拆,全局的常量或者變量不需要標記 lazy 修飾符。而局部范圍的常量或變量從不延遲計算氮墨。

  2. 類型屬性纺蛆,不論創(chuàng)建多少一個類的類實例,類型屬性都只有唯一一份规揪,所有實例共享該數(shù)據(jù)桥氏。使用 static (用于類和結構體),或者 class(用于類)關鍵字作為修飾符猛铅。

方法

  1. 方法是與某些特性類型相關聯(lián)的函數(shù)字支。譬如實例方法、類方法等奸忽。
  1. 在實例方法中修改值類型

    結構體和枚舉都是值類型堕伪,默認情況下,值類型的屬性不能在它的實例方法中被修改月杉。

    但是刃跛,如果確定需要在某個特定的方法中修改結構體或者枚舉的屬性,可以為這個方法選擇 可變 mutating 行為苛萎,然后就可以在方法內(nèi)部改變它的屬性桨昙。

    在可變方法中可以給 self 重新賦值:

    enum TriStateSwitch {
        case off, low, high
        mutating func next() {
            switch self {
            case .off:
                self = .low
            case .low:
                self = .high
            case .high:
                self = .off
            }
        }
    }
    var ovenLight = TriStateSwitch.low
    ovenLight.next()
    // ovenLight 現(xiàn)在等于 .high
    ovenLight.next()
    // ovenLight 現(xiàn)在等于 .off
    

下標

  1. 下標允許通過在實例名稱后面的方括號中傳入一個或者多個索引值來對實例進行查詢。它的語法類似于實例方法語法和計算型屬性語法腌歉。定義下標使用 subscipt 關鍵字蛙酪,與定義實例方法類似,都是指定一個或多個輸入?yún)?shù)和一個返回類型翘盖。與實例方法不同的是桂塞,下標可以設定為讀寫或只讀。這種行為由 gettersetter 實現(xiàn)馍驯,類似計算型屬性:

    subscript(index: Int) -> Int {
        get {
             // 返回一個適當?shù)?Int 類型的值
         }
        set(newValue) {
            // 執(zhí)行適當?shù)馁x值操作
        }
    }
    
  1. 實例下標是在特定類型的一個實例上調(diào)用的下標阁危。也可以定義一種在這個類型自身上調(diào)用的下標玛痊。這種下標被稱作類型下標】翊颍可以通過在 subscript 關鍵字之前寫下 static 關鍵字的方式來表示一個類型下標擂煞。類類型可以使用 class 關鍵字來代替 static,它允許子類重寫父類中對那個下標的實現(xiàn)趴乡。下面的例子展示了如何定義和調(diào)用一個類型下標:

    enum Planet: Int {
        case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
        static subscript(n: Int) -> Planet {
            return Planet(rawValue: n)!
        }
    }
    let mars = Planet[4]
    print(mars)
    
  2. 補充一個 staticclass 的區(qū)別:

    • static 可以修飾屬性对省、函數(shù),可用于類或者結構體中晾捏。但是被 static 修飾的對象不支持重寫 override蒿涎。
    • class 只用于類對象中,可以重寫惦辛。

繼承

  1. 在合適的地方劳秋,可以通過使用 super 前綴來訪問超類版本的方法、屬性或者下標:
    • 在方法 someMetho() 的重寫實現(xiàn)中裙品,可以通過 super.someMethod 來調(diào)用超類版本的 someMethod() 方法俗批。
    • 在屬性 somePropertygettersetter 的重寫實現(xiàn)中,可以通過 super.someProperty 來訪問超類版本的 someProperty 屬性市怎。
    • 在下標的重寫實現(xiàn)中,可以通過 super[someIndex] 來訪問超類版本中相同的下標辛慰。
  1. 可以把方法区匠、屬性、下標標記為 final 來防止它們被重寫帅腌。只需要在聲明關鍵字前加上 final 修飾符即可驰弄。也可以在關鍵字 class 前添加 final 修飾符,來將整個類標記為 final 速客。這樣的類是不可以被繼承的戚篙。

構造過程

  1. 類和結構體在創(chuàng)建實例時,必須為所有存儲屬性設置合適的初始值溺职。存儲屬性不能處于一個未知的狀態(tài)岔擂。當為存儲屬性設置默認值或者在構造器中設置初始值時,他們的值是被直接設置的浪耘,不會觸發(fā)任何屬性觀察者乱灵。
  1. 關于 形參命名實參標簽

    下面代碼塊中, fromFahrenheit 是實參標簽七冲,fahrenheit 是形參命名痛倚。

    struct Celsius {
        var temperatureInCelsius: Double
        init(fromFahrenheit fahrenheit: Double) {
            temperatureInCelsius = (fahrenheit - 32.0) / 1.8
        }
    }
    
  2. 可以再構造過程中的任意點給常量屬性賦值,只要在構造過程結束時它設置成正確的值澜躺。一旦長兩屬性被賦值蝉稳,它將永遠不能更改抒蚜。對于類實例來說,它的常量屬性只能在類的構造過程中被修改耘戚,不能在子類中修改削锰。

  3. 結構體如果沒有定義任何自定義構造器,它將自動獲得一個 逐一成員構造器(memberwise initializer)毕莱。不像默認構造器器贩,空手存儲型屬性有默認值,結構體也會獲得逐一成員構造器朋截。

  4. 構造器可以調(diào)用其他構造器來完成實力部分的構造過程蛹稍。這一過程成為構造器代理。

  5. Swift 為類提供了兩種構造器來確保實例中所有的存儲型屬性都能獲得初始值部服,它們被稱為指定構造器和便利構造器唆姐。指定構造器是類中最主要的構造器。其中:

    • 指定構造器必須總是 向上代理
    • 便利構造器必須總是 橫向代理
  6. 安全檢查的兩段式構造過程展示:

    階段 1

    • 類的某個指定構造器或便利構造器被調(diào)用廓八。
    • 完成類的新實例的內(nèi)存分配奉芦,但此時內(nèi)存還沒有被初始化。
    • 指定構造器確保其所在的類引入的所有存儲型屬性都已賦初值剧蹂。存儲型屬性的內(nèi)存完成初始化声功。
    • 指定構造器切換到父類的構造器,對其存儲屬性完成同樣的任務宠叼。
    • 這個過程沿著類的繼承鏈一直往上執(zhí)行先巴,直到達到繼承鏈的最頂部。
    • 當達到了繼承鏈的最頂部冒冬,而且繼承鏈的最后一個類已經(jīng)確保其所有的存儲型屬性已賦初值伸蚯,這個實例的內(nèi)存被認為已經(jīng)完全初始化,此時简烤,階段1完成剂邮。

    階段 2

    • 從繼承鏈的頂部往下,繼承鏈上每個類的指定構造器都有機會進一步自定義實例横侦。構造器此時可訪問 self 挥萌,修改它的屬性并調(diào)用實例方法等。
    • 最終丈咐,繼承鏈中任意的便利構造器都有機會自定義其實例和使用 self 瑞眼。
  7. 可失敗構造器的參數(shù)名和參數(shù)類型,不能與其他非可失敗構造器的參數(shù)名棵逊,及參數(shù)類型相同伤疙。嚴格來說,構造器都不支持返回值。因為構造器本身的作用徒像,只是確保對象能被正確的構造黍特。因此只是用 return nil 來可失敗構造器構造失敗,而不要用關鍵字 return 來表明構造成功锯蛀。

    struct Animal {
        let species: String
        init?(species: String) {
            if species.isEmpty {
                return nil
            }
            self.species = species
        }
    }
    

析構過程

  1. 析構器只適用于類類型灭衷,析構器是在實例被釋放前自動調(diào)用的。不能主動調(diào)用析構器旁涤。子類繼承父類的構造器翔曲,并且在子類的構造器實現(xiàn)的最后,父類構造器會被自動調(diào)用劈愚。

可選鏈

  1. 嗯瞳遍,就是那個 ?。這里只是說明了怎么調(diào)用屬性菌羽、方法掠械、下標等。

錯誤處理

  1. Swift 中的錯誤處理和其他語言中用 try 注祖、 catch 猾蒂、 和 throws 進行異常處理很像。和其他語言(包括 Objective-C)的異常處理不同的是是晨, Swift 中的錯誤處理不涉及解除調(diào)用棧肚菠,這是一個計算代價高昂的過程。就此而言署鸡, throw 語句的性能特性可以和 return 語句媲美案糙。
  1. 為了表示一個函數(shù)、方法或構造器可以拋出錯誤靴庆,在函數(shù)聲明后加上 throws 關鍵字。一個標有 throws 的函數(shù)稱為 throwing 函數(shù)怒医。如果這個函數(shù)指明了返回類型炉抒,throws 關鍵字要寫在返回箭頭 -> 的前面。只有 throwing 函數(shù)可以傳遞錯誤稚叹。任何在非 throwing 函數(shù)內(nèi)部拋出的錯誤都只能在函數(shù)內(nèi)部處理焰薄。
  2. do-catch 可以對錯誤進行匹配。 defer 是將代碼的執(zhí)行延遲到當前作用域退出之前扒袖。

類型轉換

  1. 類型轉換在 Swift 中使用 asis 操作符實現(xiàn)塞茅。
  2. 類型檢查操作符 is 來檢查一個實例是否屬于特定子類型。 as? 或者 as! 用來轉換季率。
  3. Swift 為不確定的類型提供了兩種類型別名:
    • Any 標識任何類型野瘦,包括函數(shù)類型。
    • AnyObject 可以表示任何類類型的實例。

嵌套類型

  1. 所謂 嵌套類型鞭光,是指可以在支持的類型中定義嵌套的枚舉吏廉、類和結構體。要在一個類型中嵌套另一個類型惰许,將嵌套類型定義在其外部類型的 { } 內(nèi)即可席覆,而且根據(jù)需要可以定義多級嵌套。(根據(jù) SwiftLint 不建議嵌套過多)

擴展

  1. 擴展可以給一個現(xiàn)有類汹买、結構體佩伤、枚舉,還有協(xié)議添加新的功能晦毙。它擁有在不訪問擴展類型源碼實現(xiàn)的基礎上就完成功能擴展的能力生巡,即逆向建模。但需要注意的是结序,擴展可以添加新的功能障斋,但是不能重寫已經(jīng)存在的功能。Swift 中的擴展可以:
    • 添加計算型實力屬性和類屬性徐鹤。
    • 定義實例方法和類方法垃环。
    • 提供新的構造器拷呆。
    • 定義下標茎芋。
    • 定義和使用新的嵌套類型。
    • 使已經(jīng)存在的類型遵循某個協(xié)議塔拳。
  1. 擴展可以添加新的計算屬性劲赠,但是不能添加存儲屬性涛目,或著向現(xiàn)有屬性添加屬性觀察者。
  2. 擴展可以添加便利構造器凛澎,但是不能添加指定構造器霹肝。

協(xié)議

  1. 有時候需要在方法中改變(或異變)方法所屬的實例,對值類型塑煎,則需要加 mutating 關鍵字沫换。如果在協(xié)議中定義了一個實例方法,該方法會改變遵循該協(xié)議的類型的實例最铁,那么在定義協(xié)議時需要在方法前添加 mutating 關鍵字讯赏。這使得結構體和枚舉能夠遵循該協(xié)議并滿足此方法要求。
  2. 協(xié)議合成冷尉,即多個協(xié)議合一通過 & 符號結合起來漱挎。用的并不是很多。
  3. 協(xié)議擴展雀哨,可以更優(yōu)雅的實現(xiàn) 可選協(xié)議 磕谅。

泛型

  1. 請始終以大寫字母開頭的駝峰命名法來為類型參數(shù)命名,以表明它是一個占位類型,而不是一個值怜庸。

  2. 類型約束指定類型參數(shù)必須繼承自指定類当犯、遵循特定的協(xié)議或協(xié)議組合。如下:

    func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
        // 這里是泛型函數(shù)的函數(shù)體部分
    }
    
  3. 關聯(lián)類型

    定義一個協(xié)議時割疾,聲明一個或多個關聯(lián)類型作為協(xié)議定義的一部分將會非常有用嚎卫。關聯(lián)類型為協(xié)議中某個類型提供了一個占位符名稱,其代表的實際類型在協(xié)議被遵循時才會指定宏榕。關聯(lián)類型通過 associatedtype 關鍵字來指定拓诸。

    protocol Container {
        associatedtype Item
        mutating func append(_ item: Item)
        var count: Int { get }
        subscript(i: Int) -> Item { get }
    }
    

    下面用一個結構體來遵循這個協(xié)議:

    struct IntStack: Container {
        // IntStack 的原始實現(xiàn)部分
        var items = [Int]()
        mutating func push(_ item: Int) {
            items.append(item)
        }
        mutating func pop() -> Int {
            return items.removeLast()
        }
        // Container 協(xié)議的實現(xiàn)部分
        typealias Item = Int
        mutating func append(_ item: Int) {
            self.push(item)
        }
        var count: Int {
            return items.count
        }
        subscript(i: Int) -> Int {
            return items[i]
        }
    }
    

    或者使用一個泛型 Stack 來遵循該協(xié)議:

    struct Stack<Element>: Container {
        // Stack<Element> 的原始實現(xiàn)部分
        var items = [Element]()
        mutating func push(_ item: Element) {
            items.append(item)
        }
        mutating func pop() -> Element {
            return items.removeLast()
        }
        // Container 協(xié)議的實現(xiàn)部分
        mutating func append(_ item: Element) {
            self.push(item)
        }
        var count: Int {
            return items.count
        }
        subscript(i: Int) -> Element {
            return items[i]
        }
    }
    

    也可以在協(xié)議里給關聯(lián)類型添加約束來要求遵循的類型滿足協(xié)議:

    protocol Container {
        associatedtype Item: Equatable
        mutating func append(_ item: Item)
        var count: Int { get }
        subscript(i: Int) -> Item { get }
    }
    

    或者在關聯(lián)類型約束里使用協(xié)議:

    protocol SuffixableContainer: Container {
        associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
        func suffix(_ size: Int) -> Suffix
    }
    
  4. ** 泛型 Where 語句**

    需要靈活使用 where 進行條件約束即可。

不透明類型

  1. 雖然使用不透明類型作為返回值麻昼,看起來和返回協(xié)議類型非常相似奠支,但兩者有個主要區(qū)別,就是是否需要保持類型一致性抚芦。一個不透明類型只能對應一種具體的類型倍谜,即便函數(shù)調(diào)用者并不知道是哪一種類型。協(xié)議類型可以對應多個類型叉抡,只要他們都遵循統(tǒng)一協(xié)議尔崔。總得來說褥民,協(xié)議類型更具靈活性季春,底層類型可以存儲更多的值,而不透明類型對這些底層類型有更強的限定性消返。用法如下:
    protocol Shape {
        func draw() -> String;
    }
    
    struct Quare: Shape {
        var size: Int
    
        func draw() -> String {
            return size.description;
        }
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        /// 返回協(xié)議類型
        func makeOneQuare() -> Shape {
            let q1 = Quare(size: 10)
            print(q1.draw())
            return q1;
        }
    
        /// 返回不透明類型
        func makeSecondQuare() -> some Shape {
            let q2 = Quare(size: 10)
            print(q2.draw())
            return q2;
        }
    }
    
  2. 說一下自己的看法吧:不透明類型是可以被協(xié)議類型替代使用的载弄,這個就好像 weakunowned 兩個關鍵詞一樣撵颊,我們有知道區(qū)別的必要性宇攻,但是即使只用 weak,就可以解決循環(huán)引用的問題了倡勇〕吲觯可能真的有只能使用 unowned 的情況,如果以后遇到译隘,會在這里補充。

自動引用計數(shù)

  1. 引用計數(shù)僅僅用于類的實例洛心。結構體和枚舉都是值類型固耘,不是引用類型,也不是通過引用的方式存儲和傳遞词身。
  2. 弱引用不會對其引用的實例保持強引用厅目,因而不會阻止 ARC 銷毀被引用的實例。這個特性阻止了引用變?yōu)檠h(huán)引用。關鍵字 weak损敷。另外葫笼,當 ARC 設置弱引用為 nil 時,屬性觀察不會被觸發(fā)拗馒。
  3. 弱引用類似路星,無主引用不會牢牢保持引用的實例。和弱引用不同的是诱桂,無主引用在其他實例有相同或更長聲明周期時使用洋丐。關鍵字是 unowned 。無主引用通常都被期望有值挥等。不過 ARC 無法再實例被銷毀后將無主引用設為 nil 友绝,因為非可選類型的實例不允許被置為 nil。并且肝劲,使用無主引用迁客,必須確保引用始終指向一個未被銷毀的實例。如果在該實例被銷毀后辞槐,訪問該實例的無主引用掷漱,會觸發(fā)運行時崩潰。
  4. 閉包和類相似催蝗,都是引用類型切威。循環(huán)引用可以通過弱引用或者無主引用來解決。

內(nèi)存安全

1. 內(nèi)存訪問沖突的實質(zhì):
  1. 內(nèi)存訪問沖突時丙号,要考慮內(nèi)存訪問上下文的三個性質(zhì):訪問是讀還是寫先朦,訪問的時長,以及訪問的地址犬缨。特別是喳魏,沖突會發(fā)生在有兩個訪問符合下列情況時:
    • 至少有一個是寫訪問
    • 他們的訪問是同一個內(nèi)存地址
    • 他們的訪問在時間線上有部分的重疊。
2. Swift 中兩種長期訪問類型:
  • In-Out 參數(shù)的訪問沖突怀薛。
  • 結構體 mutating 方法里刺彩。
3. 這個小節(jié)里舉的例子值得回頭再看看。

主要是針對長期內(nèi)存訪問的分析枝恋,和解決方法创倔。

訪問控制

1. 訪問級別

Swift 為代碼中的實體提供了五種不同的訪問級別:

  • openpublic 級別可以讓實體被同一模塊的所有實體訪問。在模塊外也可以通過導入該模塊來訪問原文件里的所有實體焚碌。通常情況下畦攘,使用 open 或者 public 來定義模塊對外訪問接口。

    open 只能作用于類或者類的成員十电,它和 public 的區(qū)別主要在于 open 限定的類或者類的成員能夠在模塊外被繼承和重寫知押。將類的訪問級別顯示指定為 open 表明已經(jīng)設計好了類的代碼叹螟,并且充分考慮到了這個類在其他模塊作為父類時的影響。

  • internal 級別讓實體被同一模塊源文件的任何實體訪問台盯,但是不能被模塊外的實體訪問罢绽。通常情況下,某個實體只在應用程序或者框架內(nèi)部使用静盅,可以將其設置為 internal 級別良价。同時,這個也是默認訪問級別温亲。
  • fileprivate 限制實體只能在其定義的文件內(nèi)部訪問棚壁。如果功能代碼的實現(xiàn)細節(jié)只需要在文件內(nèi)部訪問時,可以使用 fileprivate 來將其隱藏栈虚。
  • private 限制實體只能在其定義的作用域袖外,以及同一文件的 extension 中訪問。非同一個源文件的 extension 魂务,也是無法訪問的曼验。

以上, open 的訪問級別最高粘姜,限制最少鬓照。private 的訪問級別最低,限制最多孤紧。

2. 訪問級別規(guī)則

Swift 中訪問級別遵循一個原則:實體不能定義在具有更低級別訪問的實體中豺裆。例如:

  • 一個 public 的變量,其所在類型的訪問級別不能是 internal号显、fileprivate 或者 private 臭猜。因為無法保證變量的類型在使用變量的地方也具有訪問權限。
  • 函數(shù)的訪問級別不能高于它的參數(shù)類型和返回類型的訪問級別押蚤。因為這樣就會出現(xiàn)函數(shù)可以在任何地方被訪問蔑歌,而參數(shù)和返回類型不可以的情況。

注意揽碘,即使違反上面的規(guī)則次屠,正常情況下,也不會編譯或者運行錯誤…

高級運算符

開發(fā)中比較少需要使用高級運算符雳刺,這里留著記錄劫灶,具體使用時在參考,點擊標題即可掖桦。

1. 位運算符
  • 按位取反運算符 Bitwise NOT Operator

    • 按位取反運算符 ~ 對一個數(shù)值的全部比特位進行取反
    • 按位取反運算符是一個前綴運算符浑此,直接放在運算數(shù)之前,并且他們之間不能添加任何空格滞详。
    let age = 18
    let invertAge = ~age
    print(invertAge)  // -19
    
  • 按位與運算符 Bitwise AND Operator

    • 按位與運算符 & 對兩個數(shù)的比特位進行合并凛俱。它返回一個新的數(shù)。只有當兩個數(shù)的全部對應為都為 1 的時候料饥,新數(shù)的對應位才為 1 蒲犬。
    let one = 10
    let sec = 10
    let third = 20
    let and1 = one & sec
    let and2 = one & third
    print(and1, and2)   // 10         0
    
  • 按位或運算符 Bitwise OR Operaor

    • 按位或運算符 | 可以對兩個數(shù)的比特位進行比較。它返回一個新的數(shù)岸啡,只要兩個數(shù)的對應位中任意一個為 1 原叮,新數(shù)的對應位就為 1
  • 按位異或運算符 Bitwise XOR Operator

    • 按位異或運算符巡蘸,或稱排外的運算符 ^奋隶,可以對兩個數(shù)的比特位進行比較。它返回一個新的數(shù)悦荒,當兩個數(shù)的對應為不相同時唯欣,新數(shù)的對應位就為 1,對應位相同時搬味,則為 0 境氢。
  • 按位左移、按位右移運算符 Bitwise Left Right Shift Operators

    • 按位左移運算符 << 和按位右移運算符 >> 可以對一個數(shù)的所有位指定位數(shù)的左移或者右移碰纬,但是需要遵循下面定義的規(guī)則萍聊。
    • 對一個數(shù)進行按位左移或者按位右移,相當于對這個數(shù)進行乘以 2 或者除以 2 的運算悦析。將一個整數(shù)左移一位寿桨,等價于將這個數(shù)乘以 2 。同樣的强戴,將一個整數(shù)右移一位亭螟,等價于將這個數(shù)除以 2
    • 無符號整數(shù)的移位運算(簡單)
    • 有符號整數(shù)的移位運算(復雜)
2. 溢出運算符
  • 溢出加法 &+
  • 溢出減法 &-
  • 溢出乘法 &*
3. 優(yōu)先級和結合性

運算時酌泰,加上必要的括號媒佣,更具可讀性。

4. 運算符函數(shù)

類和結構體可以為現(xiàn)有的運算符提供自定義的實現(xiàn)陵刹,這通常被稱為 運算符重載 默伍。

  • 前綴和后綴運算符,如 -a 衰琐、b!
  • 符合賦值運算符也糊,如 =+=
  • 等價運算符羡宙,如 == 狸剃、 !=
5. 自定義運算符

自定義運算符的優(yōu)先級。

后記

本來是想一星期補完的狗热, 結果斷斷續(xù)續(xù)持續(xù)了兩周多钞馁。這里僅僅是記錄自己日常中容易忽視的虑省,或者不太常用的一些點。語言參考只是讓我們對這個語言有個大概的了解僧凰,想要加深對 Swift 的理解探颈,還是應該更多的去看優(yōu)秀的源碼,自己動手去實踐和總結训措。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伪节,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绩鸣,更是在濱河造成了極大的恐慌怀大,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呀闻,死亡現(xiàn)場離奇詭異化借,居然都是意外死亡,警方通過查閱死者的電腦和手機总珠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門屏鳍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人局服,你說我怎么就攤上這事钓瞭。” “怎么了淫奔?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵山涡,是天一觀的道長。 經(jīng)常有香客問我唆迁,道長鸭丛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任唐责,我火速辦了婚禮鳞溉,結果婚禮上,老公的妹妹穿的比我還像新娘鼠哥。我一直安慰自己熟菲,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布朴恳。 她就那樣靜靜地躺著抄罕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪于颖。 梳的紋絲不亂的頭發(fā)上呆贿,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音森渐,去河邊找鬼做入。 笑死冒晰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的母蛛。 我是一名探鬼主播翩剪,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彩郊!你這毒婦竟也來了?” 一聲冷哼從身側響起蚪缀,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤秫逝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后询枚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體违帆,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年金蜀,在試婚紗的時候發(fā)現(xiàn)自己被綠了刷后。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡渊抄,死狀恐怖尝胆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情护桦,我是刑警寧澤含衔,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站二庵,受9級特大地震影響贪染,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜催享,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一杭隙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧因妙,春花似錦痰憎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汁果,卻和暖如春涡拘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背据德。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工鳄乏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留跷车,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓橱野,卻偏偏與公主長得像朽缴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子水援,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容