Swift5.2 拾遺筆記(三)

本文為私人學習筆記萨螺,僅僅做為記錄使用决乎,詳情內(nèi)容請查閱 中文官方文檔


泛型

先看一段代碼佃迄。

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

上述代碼的作用是交換兩個 Int 的值泼差。那么,當我們現(xiàn)在需要交換兩個 String 的值時呵俏,我們需要重寫編寫類似的交換方法堆缘。那如果還需要交換其他類型的值,又該如何呢普碎?

泛型能讓你根據(jù)自定義的需求吼肥,編寫出適用于任意類型的、靈活可復用的函數(shù)及類型。你可以避免編寫重復的代碼潜沦,而是使用一種清晰抽象的方式來表達代碼的意圖萄涯。

泛型是 Swift 最強大的特性之一,很多 Swfit 標準庫是基于泛型代碼構(gòu)建的唆鸡。例如涝影,Swift 的 ArrayDictionary 都是泛型集合。

泛型函數(shù)

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

泛型函數(shù)適用于任意類型争占,它使用 占位符 類型名(這里叫做 T)燃逻,而不是實際類型。泛型函數(shù)的函數(shù)名后面跟著占位類型名 T臂痕,并使用尖括號包裹起來伯襟,這個將括號告訴 Swift 那個 T 是函數(shù)定義內(nèi)的一個占位類型名,因此 Swift 不會去查找名為 T 的實例類型握童。
T 類型參數(shù)由傳入的值的類型推斷出來姆怪,這點和 Any 類型有著很大的差別。

類型參數(shù)的命名

字典 Dictionary<Key, Value> 中的 KeyValue 及數(shù)組 Array<Element> 中的 Element澡绩,這能告訴閱讀代碼的人這些參數(shù)類型與泛型類型或函數(shù)之間的關(guān)系稽揭。然而,當它們之間沒有有意義的關(guān)系時肥卡,通常使用單個字符來表示溪掀,例如 TU步鉴、V揪胃。

泛型類型

除了泛型函數(shù),Swift 的標準庫中還有很多泛型類型氛琢,例如 DictionaryArray 喊递。Swift 還允許你自定義泛型類型,這些自定義的類阳似、結(jié)構(gòu)體和枚舉可以適用于任意類型册舞。

struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

自定義結(jié)構(gòu)體 Stack 使用了占位類型 Element,這個類型參數(shù)包裹在緊隨結(jié)構(gòu)體的一對尖括號里障般。

類型約束

類型約束指定類型參數(shù)必須繼承自指定類或者遵循特定的協(xié)議。例如盛杰,Swift 的 Dictionary 類型對字典的鍵的類型做了限制挽荡,字典的 key 必須是可哈希的(hashable),Swift 的基本類型默認都是可哈希的定拟≈暌溃可哈希的目的是為了便于檢查字典中是否已經(jīng)包含某個特定鍵的值荠藤。如果沒有這個約束念秧,那么字典將無法判斷是否可以插入或者替換某個指定鍵的值币狠,也不能查找到已經(jīng)存儲在字典中的指定鍵的值渐行。

約束語法:

在一個類型參數(shù)后面放置一個類名或者協(xié)議名蕴忆,并用冒號進行分割卓鹿,來定義類型約束杰妓。

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

關(guān)聯(lián)類型

定義一個協(xié)議時巷挥,聲明一個或者多個關(guān)聯(lián)類型作為協(xié)議的一部分將會非常有用桩卵。關(guān)聯(lián)類型為協(xié)議中的某個類型提供了一個占位符名稱,所代表的實際類型在協(xié)議被遵循實現(xiàn)時才會被指定句各。關(guān)聯(lián)類型通過關(guān)鍵字 associatedtype 來指定吸占。

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

Container 協(xié)議定義了一個關(guān)聯(lián)類型 Item,該類型并沒有指定實際類型凿宾,這個類型的確定留給了遵循該協(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]
    }
}

Stack 結(jié)構(gòu)體遵循了 Container 協(xié)議,其中占位類型參數(shù) Element 被用作 append(_:) 方法的 Item 參數(shù)和下標的返回類型初厚。Swift 可以據(jù)此推斷出 Element 的類型即是 Item 的類型件蚕。

除此之外,如果沒有在協(xié)議中涉及到關(guān)聯(lián)類型产禾,即 Swift 無法自動推斷出關(guān)聯(lián)類型的實際類型排作,你也可以在遵循協(xié)議的類型中手動完成關(guān)聯(lián)類型的實際類型。例如:

typealias Item = Int

擴展現(xiàn)有類型來指定關(guān)聯(lián)類型

我們可以通過擴展來讓一個已經(jīng)存在的類型遵循一個協(xié)議亚情,然后就可以將該類型充當這個協(xié)議來使用妄痪,這里的協(xié)議包括了使用了關(guān)聯(lián)類型協(xié)議。

Swift 的 Array 類型已經(jīng)提供了 append(_:) 方法楞件,count 屬性衫生,以及使用 Int 索引的下標來檢索其元素,這些功能都滿足了 Container 協(xié)議的要求土浸,因此我們可以通過擴展聲明其遵循了 Container 協(xié)議罪针。你可以通過一個空擴展來實現(xiàn)這點:

extension Array: Container {}

Arrayappend(_:) 方法和下標確保了 Swift 可以推斷出 Item 具體的實際類型,并且定義了這個擴展之后黄伊,你可以將任意的 Array 當作 Container 來使用泪酱。

給關(guān)聯(lián)類型添加約束

關(guān)聯(lián)類型同樣可以添加約束。

associatedtype Item: Equatable

泛型 Where 語句

泛型約束 讓你能夠為泛型函數(shù)还最、下標墓阀、類型的類型參數(shù)定義一些強制要求。

對關(guān)聯(lián)類型添加約束通常是非常有用的拓轻,你可以通過定義一個泛型 where 子句來實現(xiàn)斯撮。通過泛型 where 子句讓關(guān)聯(lián)類型遵從某個特定的協(xié)議,以及某個特定的類型參數(shù)和關(guān)聯(lián)類型必須類型相同悦即。

你可以通過將 where 關(guān)鍵字緊跟在類型參數(shù)列表后面來定義 where 子句, where 子句后跟一個或者多個針對關(guān)聯(lián)類型的約束,以及一個或者多個類型參數(shù)和關(guān)聯(lián)類型間的相等關(guān)系辜梳。你可以在函數(shù)體或者類型的大括號之前添加 where 子句粱甫。

func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: Equatable {

        // 檢查兩個容器含有相同數(shù)量的元素
        if someContainer.count != anotherContainer.count {
            return false
        }

        // 檢查每一對元素是否相等
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }

        // 所有元素都匹配,返回 true
        return true
}

具有泛型 Where 子句的擴展

可以使用泛型 where 子句作為擴展的一部分作瞄。

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

上述使用了泛型 where 子句為 Stack 擴展添加了新的條件茶宵,只有當 Stack 中的元素符合 Equatable 協(xié)議時,擴展才會添加 isTop(_:) 方法宗挥。

如果嘗試在其元素不符合 Equatable 協(xié)議的 Stack 對象上調(diào)用該方法則會收到編譯錯誤乌庶。

再比如,你可以擴展 Container 協(xié)議契耿。

extension Container where Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

新的擴展方法會確保容器至少有一個元素瞒大,然后檢查容器中的第一個元素是否與給定的元素相等。

具有泛型 Where 子句的關(guān)聯(lián)類型

可以在關(guān)聯(lián)類型后面加上具有泛型 where 的子句搪桂。例如透敌,建立一個包含迭代器(Iterator)的容器。

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

    associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
    func makeIterator() -> Iterator
}

迭代器(Iterator)的泛型 where 子句要求:無論迭代器是什么類型踢械,迭代器中的元素類型酗电,必須和容器項目的類型保持一致。makeIterator() 則提供了容器的迭代器的訪問接口内列。

一個協(xié)議繼承了另一個協(xié)議撵术,你通過在協(xié)議聲明的時候,包含泛型 where 子句话瞧,來添加了一個約束到被繼承協(xié)議的關(guān)聯(lián)類型嫩与。

protocol ComparableContainer: Container where Item: Comparable { }

泛型下標

下標可以是泛型,它們能夠包含泛型 where 子句移稳。

extension Container {
    subscript<Indices: Sequence>(indices: Indices) -> [Item]
        where Indices.Iterator.Element == Int {
            var result = [Item]()
            for index in indices {
                result.append(self[index])
            }
            return result
    }
}

這個 Container 協(xié)議的擴展添加了一個下標方法蕴纳,接收一個索引的集合,返回每一個索引所在的值的數(shù)組个粱。這個泛型下標的約束如下:

  • 在尖括號中的泛型參數(shù)Indices古毛,必須是符合標準庫中的Sequence 協(xié)議的類型。
  • 下標使用的單一的參數(shù)都许,indices稻薇,必須是Indices 的實例。
  • 泛型where 子句要求 Sequence(Indices) 的迭代器胶征,其所有的元素都是 Int 類型塞椎。這樣就能確保在序列(Sequence)中的索引和容器(Container)里面的索引類型是一致的。

綜合一下睛低,這些約束意味著案狠,傳入到 indices 下標,是一個整型的序列骂铁。

不透明類型

返回不透明類型

不透明類型和泛型相反吹零。泛型允許調(diào)用一個方法時,為這個方法的形參和返回值指定一個與實現(xiàn)無關(guān)的類型拉庵。不透明類型允許函數(shù)實現(xiàn)時灿椅,選擇一個與調(diào)用代碼無關(guān)的返回類型。

// 協(xié)議
protocol Shape {
    func draw() -> String
}
// 輸出圖形:正方形
struct Square: Shape {
    var size: Int
    func draw() -> String {
        let line = String(repeating: "*", count: size)
        let result = Array<String>(repeating: line, count: size)
        return result.joined(separator: "\n")
    }
}
// 返回不透明類型
func makeGraphics() -> some Shape {
    let square = Square(size: 2)
    return square
}

makeSquare() 函數(shù)將返回值類型定義為 some Shape 钞支,因此茫蛹,該函數(shù)返回遵循 Shape 協(xié)議的給定類型,而不需要指定任何具體類型烁挟。換句話說婴洼,該函數(shù)可以表明它公共接口的基本性質(zhì) - 返回值是一個幾何圖形,而不是由公共接口協(xié)議生成的特殊類型信夫。如 Square窃蹋。

不透明類型和協(xié)議類型的區(qū)別

雖然使用不透明類型作為函數(shù)返回值,看起來和返回協(xié)議類型非常的相似静稻,但是這兩者有一個重要的卻別:是否需要保證類型一致性警没。

// 反轉(zhuǎn)形狀
struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}

func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
    if shape is Square {
        return shape // 錯誤:返回類型不一致
    }
    return FlippedShape(shape: shape) // 錯誤:返回類型不一致
}

由于 invalidFlip(_:) 方法返回值可能存在兩種,所以該方法是不正確的振湾,為了修正該方法杀迹,我們可以將針對 Square 的特殊處理移入到 FlippedShape 中,以確保函數(shù)的返回值唯一押搪。

struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        if shape is Square {
            return shape.draw()
        }
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}
func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
    return FlippedShape(shape: shape) 
}

一個不透明類型只能對應一個具體的類型树酪,即便函數(shù)調(diào)用者并不能知道是哪一種類型;協(xié)議類型則可以同時對應多個類型大州,只要它們都遵循同一協(xié)議续语。總的來說厦画,協(xié)議類型更具靈活性疮茄,底層類型可以存儲更多的值,而不透明類型對這些底層類型有著更強的限制根暑。

不透明類型的作用

具有不透明返回類型的函數(shù)或方法會隱藏返回值的類型信息力试。函數(shù)不再提供具體的類型作為返回類型,而是根據(jù)它支持的協(xié)議來描述返回值排嫌。

在處理模塊和調(diào)用代碼之間的關(guān)系時畸裳,隱藏類型信息非常重要,因為返回的底層數(shù)據(jù)類型仍然可以保持私有淳地。而且不同于返回協(xié)議類型怖糊,不透明類型能保證類型一致性帅容,即:編譯器能獲取到類型信息,同時模塊使用者卻不能獲取到伍伤。

閉包的循環(huán)強引用

在定義閉包的同時定義捕獲列表作為閉包的一部分丰嘉,通過這種方式可以解決閉包和類實例之間的循環(huán)引用。

捕獲列表定義類閉包體內(nèi)捕獲的一個或者多個引用類型的規(guī)則嚷缭。根據(jù)具體的應用場景來使用弱引用還是無主引用。

捕獲列表

捕獲列表中的每一項都是由一對元素組成耍贾,一個元素是 weakunowner 關(guān)鍵字阅爽,另一個元素是類實例的引用。

如果閉包有參數(shù)列表和返回類型荐开,那么把捕獲列表放在它們的前面付翁。

lazy var someClosure = {
    [unowned self, weak delegate = self.delegate]
    (index: Int, stringToProcess: String) -> String in
    // 這里是閉包的函數(shù)體
}

如果閉包沒有參數(shù)列表或者返回類型,它們會通過上下文推斷晃听,那么可以把捕獲列表和關(guān)鍵字 in 放在閉包最開始的地方百侧。

lazy var someClosure = {
    [unowned self, weak delegate = self.delegate] in
    // 這里是閉包的函數(shù)體
}

弱引用 vs 無主引用

在閉包和捕獲列表的實例總是相互引用并且總是同時銷毀時,將閉包內(nèi)的捕獲定義為 無主引用能扒。相反的佣渴,如果被捕獲的引用具有更短的生命周期,可能隨時變?yōu)?nil 初斑,那么將閉包內(nèi)的捕獲定義為 弱引用辛润。

弱引用總是可選類型,并且當引用的實例被銷毀后见秤,弱引用的值會自動置為 nil砂竖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鹃答,隨后出現(xiàn)的幾起案子乎澄,更是在濱河造成了極大的恐慌,老刑警劉巖测摔,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件置济,死亡現(xiàn)場離奇詭異,居然都是意外死亡避咆,警方通過查閱死者的電腦和手機舟肉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來查库,“玉大人路媚,你說我怎么就攤上這事》” “怎么了整慎?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵脏款,是天一觀的道長。 經(jīng)常有香客問我裤园,道長撤师,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任拧揽,我火速辦了婚禮剃盾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淤袜。我一直安慰自己痒谴,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布铡羡。 她就那樣靜靜地躺著积蔚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烦周。 梳的紋絲不亂的頭發(fā)上尽爆,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音读慎,去河邊找鬼漱贱。 笑死,一個胖子當著我的面吹牛夭委,可吹牛的內(nèi)容都是我干的饱亿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼闰靴,長吁一口氣:“原來是場噩夢啊……” “哼彪笼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚂且,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤配猫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后杏死,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泵肄,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年淑翼,在試婚紗的時候發(fā)現(xiàn)自己被綠了腐巢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡玄括,死狀恐怖冯丙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遭京,我是刑警寧澤胃惜,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布泞莉,位于F島的核電站,受9級特大地震影響船殉,放射性物質(zhì)發(fā)生泄漏鲫趁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一利虫、第九天 我趴在偏房一處隱蔽的房頂上張望挨厚。 院中可真熱鬧,春花似錦糠惫、人聲如沸幽崩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至陌选,卻和暖如春理郑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咨油。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工您炉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人役电。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓赚爵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親法瑟。 傳聞我的和親對象是個殘疾皇子冀膝,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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