Swift 語言的一些功能特性

前言

寫這個文章最開始的緣由是我同窗問我你學了幾個月 Swift,感受怎麼樣,和 OC 對比有什麼特別之處,而後我說我也說很差。。本身從開始學 Swift 到如今也有很多幾個月,不少特性或者說對比 OC 比較有特別的東西也沒搞清楚,怎麼好意思誤人子弟,因此就本身四處谷歌百度寫這個東西就當本身學習累積用。git

仍是發現本身太懶了,從開始寫到寫完斷斷續續用了半個多月,有時候遇到一個不會的點光查資料就查了半天,效率也比較低。公司 iOS 組裏瞭解和使用過 Swift 的也很少,日常能請教的也只有小組長。至於寫完估計也不會用來分享到組內,反正你們都不熟悉,也懶得浪費他們的時間了。github

類型


在說其餘的以前我想先扯下無聊的強類型語言/弱類型語言,動態語言/靜態語言。express

動態語言/靜態語言:能/不能 在運行時能夠改變其結構的語言。編程

強/弱類型語言:這兩個術語並無很是明確的定義,但主要用以描述編程語言對於混入不一樣數據類型的值進行運算時的處理方式。強類型的語言遇到函數引數類型和實際調用類型不匹配的狀況常常會直接出錯或者編譯失敗;而弱類型的語言經常會實行隱式轉換,或者產生難以意料的結果。swift

(呃呃呃,至於有些文章說的動態類型/靜態類型語言,找了下維基百科並無單獨拎出來定義,除了部分語言中提到過這個,意義偏向於逐漸模糊的強弱類型)。api

上述分類中,Swift 是靜態強類型語言,OC 是動態強類型語言。Swift 語言有一個很重要的點是類型安全和推斷,先看看下面一段代碼:安全

let upperCases = ["DD", "PPP", "122", "21"]
let lowerCases = upperCases.map { $0.lowercased() }
if lowerCases.contains("dd") {
    print("contain target")
} // 這種寫法沒問題

if upperCases.map { $0.lowercased() }.contains("dd") {
    print("contain target")
} // err: Anonymous closure argument not contained in a closure

// 如下三種是加了個(),均不報錯
if (upperCases.map { $0.lowercased() }).contains("dd") {
    print("contain target")
}

if upperCases.map({ $0.lowercased() }).contains("dd") {
    print("contain target")
}
if (upperCases.map{ $0.lowercased() }.contains("dd")) {
    print("contain target")
}
複製代碼

這個類型推斷我也是懵逼,沒明白編譯器把這個表達式推斷成什麼樣的錯誤狀況,只能把它理解爲編譯器解釋的不夠完善。bash

元組(Tuples)


簡單使用

元組類型是用括號包圍,由一個逗號分隔的零個或多個類型的列表(只有一個值時會被推斷爲圓括號操做符,而非元組),這些值能夠不加標籤,直接用下標訪問。若是爲了意義明確,也能夠爲它加個標籤,訪問時就能夠直接用標籤訪問。網絡

元組和閉包、元類型等都是非正式類型,非正式類型時相對於結構體等正式類型而言,沒有具體的類型名,雖然元組看起來比較像匿名結構體。閉包

let t: (String, Int, Double) = ("dd", 33, 33)
print(t.0, t.1, t.2) 

let color: (r: Int, g: Int, b: Int, a: CGFloat) = (1, 2, 3, 0.1)
print(color.r, color.g, color.b, color.a)
複製代碼

合成和分解

由於元組是由多個值以很是簡單的方式構成,因此分解起來也比較簡單,這時候就建立了三個臨時變量:

let t: (String, Int, Double) = ("dd", 33, 33)
let (a, b, c) = t
print(a, b, c)
複製代碼

經過上面的合成分解,讓 Swift 在交換兩個變量時會更加簡單:

var j = 1, k = 3
(j, k) = (k, j)
print(j, k) // j = 3, k = 1
複製代碼

其實就是至關於先把 j 和 k 的值存到元組對象裏,而後再從元組對象中取值

比較

元組的對比須要參數類型一致而且遵循 Equatable, 才能夠用 ==,遵循 Comparable, 能夠用 >< 等。須要注意的是對比大小時是按順序來,得出對比結果。

空元祖

空元祖就是 (),裏面沒有元素,也就是你們更熟悉的 Void。關於 Void 裏面一些詭異的設定,Matt 大神寫過 一篇文章SwiftGG 翻譯過 這篇文章,有興趣的能夠看看

已被刪除的用法:結構體初始化和做爲函數參數

這兩個用法給出的廢棄緣由是和 Swift 的簡單易讀的風格不搭,大概是 Swift 2.2 仍是 3.0 的更新改的,文檔連接已經記不清了,Google 下就能出來。

其實元組把每一個參數加上標籤後和函數的參數部分的結構基本一致,只是函數能夠加別名,若是使用 _ 來忽略參數標籤名,那就和元組沒加標籤同樣

func sum(x: Int, y: Int) -> Int {
  return x + y
}
let params = (1,1)
sum(params)

//  初始化結構體
struct User {
    var name: String
    var age: Int
}
let t = (name: "dd", age: 12)
let user = User(t)
複製代碼

Optional


類型是枚舉:public enum Optional<Wrapped> : ExpressibleByNilLiteral。定義很簡單,可能有值也可能沒有,官方文檔上有更形象的說明,我也懶得照搬了。

解包

咱們開發的時候大可能是用具體的值,這時候就須要把 optional 解析成具體值或者空,也就是解包,通常有三種方法: if/gurad let??!。採用 ! 強制解包時若是失敗會引發 Crash。

let a: String? = "dd"
if let b = a {
    print(type(of: a), type(of: b)) // Optional<String>, String
}

let c = a ?? "default"

let d: String?
print(d!) // 運行時錯誤 Crash
複製代碼

多重可選值

這個見得比較少,形式像這種:String??。爲了更加形象的理解這個多重可選值,這裏 Optional 能夠把比成一個裏面可能有東西的黑盒子,而 String??能夠理解成一個盒子 A,盒子裏可能爲空,也多是另外一個盒子 B,而盒子 B 裏面多是空,也多是一個 String (可選鏈不會增長可選層數)。理解了這兩句話再看看下面這段代碼:

var aNil:String? = nil
var anotherNil: String?? = aNil
var literaNil: String?? = nil
print(anotherNil, literaNil) // Optional(nil) nil
複製代碼

根據上面的盒子例子能夠理解,anotherNil 爲盒子 A, 盒子 A 裏有個盒子 B 即 aNil 是空,因此這個盒子。對於 literaNil 這個盒子裏就是空,沒有其餘盒子。

至於用法,我也沒怎麼見過,只在 optionalmap 函數中見過。

public enum Optional<Wrapped> : ExpressibleByNilLiteral {
    public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
    public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
}
let s: String? = "1223"
let optionalMapped = s.map { Int($0) } // Optional(Optional(1223))
let optionalFlatMapped = s.flatMap { Int($0) } // Optional(1223)
複製代碼

當返回值 U 是非 Optional 類型時,兩者沒有區別,返回的都是 Optional(1223);當 UOptional 類型, map 函數返回的則是 Optional(Optional(Value))

一些擴展

這個黑盒子的存在,致使咱們使用時須要進行各類判斷,當邏輯比較複雜的時候,代碼會看起來比較囉嗦,這篇文章 列舉了不少實用的例子(雖然裏面有些例子我以爲直接用系統的解包更好),這裏從裏面挑個感受比較好玩的

extension Optional {
    /// 當可選值不爲空時,解包並返回參數 `optional`
    func and<B>(_ optional: B?) -> B? {
		guard self != nil else { return nil }
	    return optional
    }

    /// 解包可選值,當可選值不爲空時,執行 `then` 閉包,並返回執行結果
    /// 容許你將多個可選項鍊接在一塊兒
    func and<T>(then: (Wrapped) throws -> T?) rethrows -> T? {
		guard let unwrapped = self else { return nil }
	    return try then(unwrapped)
    }

    /// 將當前可選值與其餘可選值組合在一塊兒
    /// 當且僅當兩個可選值都不爲空時組合成功,不然返回空
    func zip2<A>(with other: Optional<A>) -> (Wrapped, A)? {
		guard let first = self, let second = other else { return nil }
	    return (first, second)
    }

    /// 將當前可選值與其餘可選值組合在一塊兒
    /// 當且僅當三個可選值都不爲空時組合成功,不然返回空
    func zip3<A, B>(with other: Optional<A>, another: Optional<B>) -> (Wrapped, A, B)? {
		guard let first = self,
	      	let second = other,
	      	let third = another else { return nil }
		return (first, second, third)
    }
}

// 使用前
if user != nil, let account = userAccount() ...

// 使用後
if let account = user.and(userAccount()) ...

// 正常示例
func buildProduct() -> Product? {
  if let var1 = machine1.makeSomething(),
    let var2 = machine2.makeAnotherThing(),
    let var3 = machine3.createThing() {
    return finalMachine.produce(var1, var2, var3)
  } else {
    return nil
  }
}

// 使用擴展
func buildProduct() -> Product? {
  return machine1.makeSomething()
     .zip3(machine2.makeAnotherThing(), machine3.createThing())
     .map { finalMachine.produce($0.1, $0.2, $0.3) }
}
複製代碼

??

這是個中置運算符,因爲運算符不能歸屬給某個類型的特性就放在了 optional 外面(雖然除了它也沒什麼東西用這個)。這個運算符使用了自動閉包(一個沒參數的閉包,調用時會直接返回值)的特性。

public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T

public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
複製代碼

這兩個的區別就是 defaultValue 也就是 ?? 後面的閉包返回值類型,其實也就是說當 optional 爲空時,defaultValue 是什麼類型,?? 就返回值什麼類型,固然這個 defaultValue 只能是 T 或者 T?。因此右邊的值是非 optional 才能解包成功。

枚舉


枚舉是 Swift 的核心對象,工程中基本都會用到它,上面那個 Optional 就是枚舉 在 OC 中用枚舉有一個不爽的就是隻能表示 Int 類型,有時須要把它和 String 相互轉換,而 Swift 中的枚舉就強大的多:

  • 能夠表示全部字面量(雖然只有四個,若是想用 CGSize 這種,須要讓 CGSize 遵循 StringLiteralConvertible 這種協議)
  • 能夠爲枚舉添加函數和計算屬性
  • 嵌套使用
  • case 支持帶參定義
  • 枚舉裏 case 類型相同的話能夠經過 rawValue 獲取具體的值
  • ...
public enum ResultCode {
    case success
    case failure(code: BusinessCode)
    case networkStatus(code: Int)
    case unknow
    
    public enum BusinessCode: String {
        case systemException = "ss"
        case permissionLimited = "pp"
        
        var cv: Int {
            if self == .systemException {
                return 1
            }
            return 2
        }
    }
    
    func dd() -> Int {
        switch self {
        case .success:
            return 0
        case .failure(let codeValue):
            return codeValue.cv
        case .networkStatus(let codeValue):
            return codeValue
        case .unknow:
            return 99
        }
    }
}

let e: ResultCode = .unknow
e.dd()
複製代碼

上面這個有點繁瑣的例子大概展現了剛纔列舉枚舉的功能。此外 Swift 4.2 引入了一個新的 protocol:CaseIterable,可用於合成簡單枚舉裏類型全部的值: allCases靜態屬性:

public protocol CaseIterable {

    /// A type that can represent a collection of all values of this type.
    associatedtype AllCases : Collection where Self == Self.AllCases.Element

    /// A collection of all values of this type.
    static var allCases: Self.AllCases { get }
}

enum Weekday : String, CaseIterable {
    case monday, tuesday, wednesday, thursday, friday
}

print(Weekday.allCases)
複製代碼

從上面協議的定義能夠看出這個自動合成的屬性是個 Collection,而且裏面的元素都是 Self 類型 (其實打印下類型就能發現這個 Collection 就是 Array),至關於 [Self]。有點很差的是隻能合成上面那種簡單屬性,不能合成帶關聯值的好比上面那個 ResultCode,這時候只能本身提供這個屬性了。

enum MartialStatus : CaseIterable {
    case single
    case married(spouse: String)
    
    static var allCases: [MartialStatus] {
        return [.single, .married(spouse: "Leon")]
    }
}
複製代碼

還有個不經常使用的東西是遞歸枚舉,下面這段代碼代碼是官方提供的,知道有這個東西看看就好,須要用的時候在具體看下。下面這段代碼用來實現一個表達式: (5 + 4) * 2

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

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))
// 打印「18」
複製代碼

另外 Swift 5 裏更新了枚舉的一個未知值警告。只有使用某些列舉系統枚舉時會這樣,緣由是編譯器規定列舉枚舉時若是沒用 default 的話就必須列舉完。那對於系統的一個枚舉,若是 iOS 13 給某個枚舉新增了一個枚舉值,就會致使老代碼沒列舉全而報錯。 這裏說某些是由於像 ComparisonResult 這種比較大小定義是永遠不會增改的,因此也不會報警告。點擊警告會補充一個 @unknown default: 固然也不是使用時都加個 default 來避免這個,好比 Equatable 協議裏就不適合用 default

func configure(for sizeClass: UIUserInterfaceSizeClass) {
        switch sizeClass {
        case .unspecified: break
        case .compact: break
        case .regular: break
        }
    }

// Warning: Switch covers known cases, but 'UIUserInterfaceSizeClass' may have additional unknown values, possibly added in future versions
複製代碼

最後若是想了解枚舉底層的東西,能夠看下 這篇文章

泛型


泛型是 Swift 最強大的特性之一,不少 Swift 標準庫是基於泛型代碼構建的。實際上,即便你沒有意識到,你也一直在語言指南中使用泛型。例如,Swift 的 ArrayDictionary 都是泛型集合。這段話是官方文檔上的。

寫完這部分再回來補了這一小段,這裏除了使用介紹和一個命名空間例子也沒說其餘強大的用法,可是工程中泛型的使用很頻繁,感受是屬於那種就是看起來不是很複雜可是巧妙使用會效果很好的東西。有個很不錯的例子是 Promisskit ,裏面使用了不少泛型,很巧妙。

基本介紹

泛型使用 佔位符 來限定爲某一類型(好比說 IntStringCollection),在其做用域的出現的該 佔位符 均限定爲相同的類型。申明是寫法爲:<佔位符A, 佔位符B>佔位符 能夠是任意字母組合,可是通常來講命名須要具備意義,好比集合中的泛型申明就叫 Element。 下面經過幾個例子看下。

// 泛型函數
func swapTwoValues<T>(_ a: inout T, _ b: inout T)

// 泛型類和結構體
struct Stack<Element> {
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
}

// 對泛型進行約束
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 這裏是泛型函數的函數體部分
}

// 使用 where 進行泛型限定 (T,U 都是集合,而且集合內的 element 類型相同)
func someFunction<T, U>(someT: T, someU: U) where T: Collection, U: Collection, T.Element == U.Element {}

// swift 5.0 加入的一個泛型枚舉
public enum Result<Success, Failure> where Failure : Error {
    case success(Success)
    case failure(Failure)
}
複製代碼

泛型關聯

在申明協議時,能夠聲明一個或多個關聯類型做爲協議定義的一部分,關聯類型經過 associatedtype 關鍵字來指定,該泛型當實際類型在協議被遵循時纔會指定。

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

// 關聯到 Int
struct IntStack: Container {
    typealias Item = Int
}

// 將關聯泛型放到類申明
struct Stack<Element>: Container {}
複製代碼

使用泛型實現系統類的自定義擴展的命名空間

說這個以前順帶提下命名空間的事,根據 Swift 系統類和一些主流的第三方庫申明能夠看出 Swift 的命名通常不須要加項目或者模塊前綴,由於能夠經過靜/動態庫名的點語法訪問來避免 duplicated symbols 問題。好比 Swift 5.0 新增了一個 Result 枚舉,而日常使用的 Alamofire 也有一個 Result ,因此使用時能夠用 ResultAlamofire.Result 來區分。固然若是是直接寫不加前置命名空間的話,優先推斷本身模塊內的結構,其次是系統的,最後是三方庫。

下面直接拿咱們 SwfitTips 裏的內容來展現,第一眼看起來有點繞,多看兩遍也就懂了,最後實現系統類擴展的調用 :image.xx.imageInfo

/// Wrapper for Kingfisher compatible types. This type provides an extension point for
/// connivence methods in Kingfisher.
public struct KingfisherWrapper<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

/// Represents a type which is compatible with Kingfisher. You can use `kf` property to get a
/// value in the namespace of Kingfisher.
public protocol KingfisherCompatible { }

public extension KingfisherCompatible {
    
    /// Gets a namespace holder for Kingfisher compatible types.
    public var kf: KingfisherWrapper<Self> {
        get { return KingfisherWrapper(self) }
        set { }
    }
}

extension Image: KingfisherCompatible { }
extension ImageView: KingfisherCompatible { }
extension Button: KingfisherCompatible { }

//usage
extension KingfisherWrapper where Base: Image {
    var imageInfo: String { return "image info" }
}

let image = UIImage()
image.kf.imageInfo
複製代碼

步驟:

  • 基於原類型使用 struct 作再次封裝
  • 定義適配協議,並使用 extension 爲其提供默認實現
  • 對自定義的 struct 作方法擴展

Protocol


協議提及來比較簡單,能夠方法、屬性申明,再經過 extension 提供默認實現,因此能夠更形象的實現多繼承了,做爲類型使用時和類、結構體基本同樣。

Swift 中更推崇面向協議編程,Swift 標準庫中不少都是用協議實現的,好比 Collection等。協議這種輕量級的結構讓編碼會更加輕鬆。邏輯也會更清楚,對於它來講,用來抽象項目中的邏輯造成一套規則比協議自身的語法運用更加劇要。

下面看一段代碼和註釋簡單介紹下用法。

protocol MyProtocol {
    /// 協議
    func defaultImplementation()
    /// 定義的時候須要考慮是否要值類型來異變
    mutating func mutatFunction()
    /// 默認 required,用一個空實現來造成可選函數
    func optFunction()
}

extension MyProtocol {
    /// 協議
    func defaultImplementation() {
        print("default protocol implementation")
    }
    mutating func mutatFunction() {
        print("mutatFunction")
    }
    
    func optFunction() {}
}

class MyClass {
    /// father
    func defaultImplementation() {
        print("default father implementation")
    }
}

class MySubClass: MyClass, MyProtocol {}

let sub = MySubClass()
// 這裏根據點語法會提示兩個函數,可是點擊哪一個都是進入 father 裏的
sub.defaultImplementation()
複製代碼

閉包


閉包和 OC 的 block 以及一些匿名函數差很少,在 Swift 中, 函數是一個特殊的閉包,主要在於值捕獲的範圍區別,所以不少函數的參數若是是一個閉包的話,能夠傳函數用來代替閉包,例如 sort 等函數,下面介紹一些基本寫法。

// 閉包屬性 聲明時不能加標籤,標準結構
var callbcak: (String, String) -> Bool = { (param1: String, param2: String) in
      return param1 > param2
}
// 尾隨閉包的簡化流程
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
func sort(by areInIncreasingOrder: (String, String) -> Bool) 

// 完整的書寫方法
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

// 簡化1:系統會根據 names 的類型推斷出參數 s1,s2
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

// 簡化1:在使用尾隨閉包時,你不用寫出它的參數標籤,而且放在 () 外面,只有一個參數時,() 能夠省略,$0 和 $1 分別表示第一個和第二個傳參
reversedNames = names.sorted() { $0 > $1 }

// 因爲 sort 函數的參數是個閉包, > 也是和閉包類型相同的函數(Comparable),因此能夠直接用 
reversedNames = names.sorted(by: >)
複製代碼

尾隨閉包上面的例子中已簡單介紹過了,也沒什麼複雜的用法。

自動閉包

自動閉包是一種不接受任何參數,被調用時返回被包裝在其中的表達式的閉包,用 @autoclosure 標記。這種語法可以省略閉包的花括號,用一個普通的表達式來代替顯式的閉包。

// customersInLine is ["Ewa", "Barry", "Daniella"]
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 打印「Now serving Ewa!
複製代碼

上面說過的 ?? 函數就利用了自動閉包的特性。

逃逸閉包

閉包可分爲 逃逸閉包非逃逸閉包,用 @escaping 來標記是 逃逸閉包。舉個例子,好比一個函數接受了一個閉包做爲參數,若是閉包在該函數的做用域內執行,那這就是 非逃逸閉包,若是閉包被賦值給了對象的屬性用於函數的做用域以外執行,就屬於 逃逸閉包,須要加 @escaping 來標記。

事實上 Swift 在使用屬性時會隱式的調用 self 這個東西,因此咱們正常狀況下能夠不寫,可是對於 逃逸閉包 捕獲的屬性,必須顯示聲明 self。對於 非逃逸閉包,編輯器會在函數做用域結束時自動釋放閉包捕獲的值(由於閉包已經用完,因此捕獲的值沒有用了)。

Swift 3.0 以後閉包默認是非逃逸的。代碼中常見的閉包屬性、網絡請求返回等都是 逃逸閉包 。此外可選類型的閉包老是逃逸的,而且不能顯示申明逃逸關鍵字。

class A {
    
    var complete: (() -> String)?
    var value: String?
    
    var b: B?
    
    func nonclosureEscaping(with complete: () -> String) {
        value = complete()
    }
    
    func closureEscaping(with complete: @escaping () -> String) {
        self.complete = complete
    }
    
    func optionalClosure(with complete: (() -> String)?) {
        guard let c = complete else { return }
        value = c()
    }
}

class B {
    var value: String?
    func testBegin() {
        let a = A()
        a.nonclosureEscaping { () -> String in
            return value!
        }
        
        a.closureEscaping { () -> String in
            return self.value!
        }
        
        a.optionalClosure { () -> String in
            return self.value!
        }
    }
}

let b = B()
b.value = "ddd"
b.testBegin()
複製代碼

函數派發機制


函數派發機制至關於 OC 裏的方法尋址,只是原理不一樣,用於找尋方法具體的執行對象,是一個很是基礎的知識點,日常遇到這方面的問題比較少,也不怎麼會關心這個。

Swift 中函數派發方式有三種:

  • static dispatch:靜態派發(直接派發)
  • table dispatch:函數表派發, 經過 SIL 分析,swift 中有兩種函數表,協議用的 witness_table,類用的 virtual_table
  • message dispatch:消息派發,OC 中經常使用的派發方式

這裏直接說結論和注意點

dispatch.png

  • 引用的類型決定了派發的方式。
  • NSObjectClass 和 Class 沒什麼區別(至少編譯器未優化前是這樣)

根據上面的結論看下下面兩段代碼

protocol MyProtocol {}

extension MyProtocol {
    func testFuncA() {
        print("protocl - funcA")
    }
}

class Myclass: MyProtocol {
    func testFuncA() {
        print("class - funcA")
    }
}

let x: MyProtocol = Myclass()
x.testFuncA() // protocl - funcA
複製代碼

因爲 x 是協議類型,因此先會去查協議的函數表,而協議裏並無 funcA 的聲明,因此協議函數表裏就不存在這個方法,也不會去根據表查找實現,就走了協議的 extension 的直接派發。

protocol MyProtocol {
	func testFuncA()
}
... (和上面同樣)

let x: MyProtocol = Myclass()
x.testFuncA() // class - funcA
複製代碼

因爲協議函數表裏聲明瞭 funcA,因此用協議函數表查找實現,找到了在 Myclass 中的實現,走的函數表派發。

至於驗證過程,須要用 SIL 一種編譯中間語言,感興趣的能夠看看最下面的參考連接

關於動態性


上面也說過,Swift 是一門靜態語言,和 OC 不一樣,不能在運行時作不少複雜的操做,這也是 Swift 被吐槽比較多的一點。 Swift 雖然不能像 OC 同樣在運行時作不少事,可是仍是作一些小動做,好比和 Java 相似的反射機制等。下面主要討論下 Swift 能作哪些相對來講具備 動態 特性的事。

首先說一個你們都知道的東西:@objc ,它能夠標記函數和類、協議等等。這個標記表示該方法或者該類能夠被用於運行時,例如在 Swift 中相似按鈕點擊的 selector 都必須用 @objc 標記,表示該方法是運行時調用(按鈕點擊是運行時事件,編譯器在編譯時對沒有調用到的非運行時函數會優化掉),同時能夠作其餘好比 KVO 這種在運時的操做。因此加了 @objc 後可用於 OC 代碼裏。在 Swift 4 以後的版本里,類繼承 NSObjct 不會再默認帶 @objc 標記,當重寫 NSObjct 編輯器會自動提示帶上 @objc。 這部分用代碼也說不了什麼就不貼了。

除了上面用 @objc 標記這種比較惡趣味的方法外,還能夠經過 Reflection 即反射來實現一些諸如運行時獲取類型、成員信息,調用任意方法等行爲。Reflection 主要使用 Mirror 這個結構體來作一些事情。

先看下最簡單的用法,動態獲取一個類的成員信息:

class User {
    var name: String = "eeee"
    var age: Int = 122
}

let u = User()
let um = Mirror(reflecting: u)
for child in um.children {
    guard let label = child.label else { continue }
    print(label, child.value)
}
//name eeee
//age 122
複製代碼

看到這裏應該就能聯想到 OC 裏的模型和 JSON 互轉的一些第三方庫, Swift 裏這種方法也是能夠用來作模型轉 JSON,可是不能用於 JSON 轉模型(除非你讓模型繼承 NSObject,而後採用 KVC 賦值這種肉眼可見的 OC 動態),不過 JSON 轉模型能夠用其餘方法作,好比 像 HandyJson作法 同樣,原理是經過計算屬性相對偏移量來賦值,比較複雜(只在 Github 瞟了幾分鐘,沒太看懂)。可是如今能夠直接使用系統的 Codable 功能。

type(of:)

Swift 每一個類型都關聯着一個元類型,大多數元類型是個全局的單例對象,可是 Class 類型遵循子類型的規則,因此沒有存全局的單例對象。

type<T, Metatype>(of value: T) -> Metatype 用於獲取對象運行時類型,它能夠獲取結構體、枚舉、以及大多數協議類型的實例,除了協議類型的泛型。也就是說這個方法的參數是個泛型,你能夠傳具體的的結構體、類等,也能夠傳一個類型是結構體、類、枚舉的泛型,可是不能傳協議類型的泛型。有點繞口,直接看下面註釋文檔的代碼

func printGenericInfo<T>(_ value: T) {
    let x = type(of: value)
    print("'\(value)' of type '\(x)'")
}

func betterPrintGenericInfo<T>(_ value: T) {
    let x = type(of: value as Any)
    print("'\(value)' of type '\(x)'")
}

func otherPrintGenericInfo<T>(_ value: T) {
    let x = type(of: value as! P)
    print("'\(value)' of type '\(x)'")
}

protocol P {}
extension String: P {}

let stringAsP: P = "Hello!"
printGenericInfo(stringAsP) // print: 'Hello!' of type 'P'
betterPrintGenericInfo(stringAsP) // print: 'Hello!' of type 'String'
otherPrintGenericInfo(stringAsP) // print: 'Hello!' of type 'String'
複製代碼

能夠看出來前面兩個方法惟一的區別是第二個方法強制轉成 Any 這種。

This unexpected result occurs because the call to `type(of: value)` inside `printGenericInfo(_:)` must return a metatype that is an instance of `T.Type`, but `String.self` (the expected dynamic type) is not an instance of `P.Type` (the concrete metatype of `value`). To get the dynamic type inside `value` in this generic context, cast the parameter to `Any` when calling `type(of:)`

根據上面的文檔,解釋說第一個方法中 type(of:) 返回的必須是 T.Type, 而 String.self 並非 P.Type (不懂爲何不是,extension 不算嗎),這個解釋仍是有點不能理解。

其餘一筆帶過的小點


Codable

一開始想介紹下這個東西的,按照原來的打算,是準備把 codingKeyKeyDecodingStrategy 以及對枚舉的 Codable 說一下的。可是在看了喵神的 這個博客 後,加上以前對 Mirror 的一些接觸,以爲只是說上面那些簡單的用法沒什麼意義,轉而想的是爲何 Codable 能夠只讓類型只遵循一個協議就能作到 JSON 編碼解碼,還有就是上面喵神連接說的重寫 _JsonEncoder 方法來去除模型字典互轉時多的那層 data 操做。

static/class

  • class 只能被用於類裏,static 能夠被用於 class/struct/protocol/enum
  • static 能夠用於修飾存儲屬性,class 不行
  • static 修飾的類方法不能被繼承,class 能夠,static 至關於 final class

==/:

這裏說的是用在 where 後面時二者的區別,通常狀況下 == 是判斷左右相等,: 是用於限定類型,從語境上就能區分區別。當使用在 extension 時,二者的區別在於: 若是擴展的是值類型,則必須用 ==;若是擴展的是引用類型,則使用 == 僅對本類有效對子類無效,使用 : 則對本類和子類均有效。

Any/AnyObject

後者只能表示任意的 class 類型,AnyClassAnyObject 的元類型。

不明白的地方

  1. 逃逸閉包爲何要顯示的聲明 self
  2. 爲何函數表的方式沒法動態生成函數插入到表裏,消息派發機制能夠?消息派發機制如何動態添加到函數表的,暫時猜想 runtime 的函數表是多個表的集合,不太懂。
  3. 編輯器有沒有方法在靜態編譯時把運行時觸摸事件的處理函數編譯到函數表中,而且在運行時調用到

引用

相關文章
相關標籤/搜索