Why Swift? Generics(泛型), Collection(集合類型), POP(協議式編程), Memory Management(內存管理)

前言

寫這篇文章主要是為了給組內要做的分享準備內容。這段時間幾個項目都用到 Swift,在上次 GIAC 大會上就被問到為什么要用 Swift,正好這個主題可以聊聊 Swift 的哪些特性吸引了我。

泛型

先來個例子看下泛型是解決什么問題的。

let nations = ["中國", "美國", "日本"]
func showNations(arr : [String]) {
    arr.map { str in
        print("\(str)")
    }
}

我們先定一個字符串數組,然后把里面的字符串打印出來。這里的 map 寫法還可以優化下:

arr.map { print("\($0)") }

那么還能做什么優化呢。將 showNations 的入參數組泛型以支持多類型,比如 [int],[double] 等。

func showArray<T>(arr: [T]) {
    arr.map { print("\($0)") }
}

可以看出泛型能夠很方便的將不同類型數據進行相同操作的邏輯歸并在一起。

類型約束

先看下我的 HTN 項目里狀態機的 Transition 結構體的定義

struct HTNTransition<S: Hashable, E: Hashable> {
    let event: E
    let fromState: S
    let toState: S
    
    init(event: E, fromState: S, toState: S) {
        self.event = event
        self.fromState = fromState
        self.toState = toState
    }
}

這里的 fromState,toState 和 event 可以是不同類型的數據,可以是枚舉,字符串或者整數等,定義 S 和 E 兩個不同的泛型可以讓狀態和事件類型不相同,這樣接口會更加的靈活,更容易適配更多的項目。

大家會注意到 S 和 E 的冒號后面還有個 Hashable 協議,這就是要求它們符合這個協議的類型約束。使用協議的話可以使得這兩個類型更加的規范和易于擴展。

Swift 的基本類型 String,Int,Double 和 Bool 等都是遵循 Hashable 的,還有無關聯值的枚舉也是的。Hashable 提供了一個 hashValue 方法用在判斷遵循協議對象是否相等時用。

Hashable 協議同時也是遵守 Equatable 協議,通過實現 == 運算符來確定自定義的類或結構是否相同。

關聯類型

在協議里定義的關聯類型也可以用泛型來處理。比如我們先定義一個協議

protocol HTNState {
    associatedtype StateType
    func add(_ item: StateType)
}

采用非泛型的實現如下:

struct states: HTNState {
    typealias StateType = Int
    func add(_ item: Int) {
        //...
    }
}

采用泛型遵循協議可以按照下面方式來寫:

struct states<T>: HTNState {
    func add(_ item: T) {
        //...
    }
}

這樣關聯類型也能夠享受泛型的好處了。

類型擦除

但是在使用關聯類型的時候需要注意當聲明一個使用了關聯屬性的協議作為屬性時,比如下面的代碼:

class stateDelegate<T> {
    var state: T
    var delegate: HTNState
}

先會提示 no initializers 的錯誤,接著會提示 error: protocol 'HTNState' can only be used as a generic constraint because it has Self or associated type requirements 。意思是 HTNState 協議只能作為泛型約束來用,因為它里面包含必需的 self 或者關聯類型。

那么該如何處理呢?這里需要通過類型擦除來解決,主要思路就是加個中間層在代碼中讓這個抽象的類型具體化。實際上在 Swift 的標準庫里就有類型擦除很好的運用,比如 AnySequence 的協議。

Where 語句

函數,擴展和關聯類型都可以使用 where 語句。where 語句是對泛型在應用時的一種約束。比如:

func stateFilter<FromState:HTNState, ToState:HTNState>(_ from:FromState, _ to:ToState) where FromState.StateType == ToState.StateType {
    //...
}

這個函數就要求他們的 StateType 具有相同類型。

泛型和 Any 類型

這兩個類型看起來很相似,但是一定要小心兩者的區別。他們區別在于 Any 類型會避開類型的檢查,所以盡量少用最好不用。泛型一方面很靈活一方面也很安全,下面舉個例子感受下兩者的區別:

func add<T>(_ input: T) -> T {
    //...
    return input;
}

func anyAdd(_ input: Any) -> Any {
    //...
    return input;
}

這兩個函數都是可以允許任意類型的 input 參數,不同在于返回的類型在 anyAdd 函數里是可以和入參不一樣的,這樣就會失控,在后續的操作中容易出錯。

集合

基本概念

先來了解下集合的基本概念,首先集合是泛型的比如:

let stateArray: Array<String> = ["工作","吃飯","玩游戲","睡覺"]

集合它需要先有個遍歷的功能,通過 GeneratorType 協議,可以不關注具體元素類型只要不斷的用迭代器調 next 就可以得到全部元素。但是使用迭代器沒法進行多次的遍歷,這時就需要使用 Sequence 來解決這個問題。像集合的 forEach,elementsEqual,contains,minElement,maxElement,map,flatMap,filter,reduce 等功能都是因為有了 Sequence 的多次遍歷。

最后 Collection 概念是因為 Sequence 無法確定集合里的位置而在 Sequence 的基礎上實現了 Indexable 協議。有了 Collection 就可以確定元素的位置,包括開始位置和結束位置,這樣就能夠確定哪些元素是已經訪問過的,從而避免多次訪問同一個元素。還能夠通過一個給定的位置直接找到那個位置的元素。

以上描述如下圖:


屏幕快照 2018-01-22 21.39.27.png

迭代器

Swift 里有個簡單的 AnyIterator<Element> 結構體

struct AnyIterator<Element>: IteratorProtocol { 
    init(_ body: @escaping () -> Element?)
    //...
}

AnyIterator 實現了 IteratorProtocol 和 Sequence 協議。通過下面的例子我們來看看如何使用 AnyIterator :

class stateItr : IteratorProtocol {
    var num:Int = 1
    func next() -> Int?{
        num += 2
        return num
    }
}

func findNext<I: IteratorProtocol>( elm: I) -> AnyIterator<I.Element> where I.Element == Int
{
    var l = elm
    print("\(l.next() ?? 0)")
    return AnyIterator { l.next() }
}

findNext(elm: findNext(elm: findNext(elm: stateItr())))

首先是定義個遵循了 IteratorProtocol 并實現了 next 函數的類。再實現一個 AnyIterator 的迭代器方法,這樣通過這個方法的調用就可以不斷的去找符合的元素了。

這里有個對 where 語句的運用,where I.Element == Int。如果把這句改成 where I.Element == String 會出現下面的錯誤提示

Playground execution failed:

error: MyPlayground.playground:18:37: error: cannot invoke 'findNext(elm:)' with an argument list of type '(elm: stateItr)'
findNext(elm: findNext(elm: findNext(elm: stateItr())))
                                    ^

MyPlayground.playground:11:6: note: candidate requires that the types 'Int' and 'String' be equivalent (requirement specified as 'I.Element' == 'String' [with I = stateItr])
func findNext<I: IteratorProtocol>( elm: I) -> AnyIterator<I.Element> where I.Element == String
     ^

編譯器會在代碼檢查階段通過代碼跟蹤就發現類型不匹配的安全隱患,這里不得不對 Swift 的設計點個贊先

Sequence

上面的迭代器只會以單次觸發的方式反復計算下個元素,但是對于希望能夠重新查找或重新生成已生成的元素,這樣還需要有個新的迭代器和一個子 Sequence。在 Sequence 協議里可以看到這樣的定義:

public protocol Sequence {
    //Element 表示序列元素的類型
    associatedtype Element where Self.Element == Self.Iterator.Element
    //迭代接口類型
    associatedtype Iterator : IteratorProtocol
    //子 Sequence 類型
    associatedtype SubSequence
    //返回 Sequence 元素的迭代器
    public func makeIterator() -> Self.Iterator
    //...
}

重新查找靠的是這個新的迭代器,而對于切片這樣的會重新生成新 Sequence 的操作就需要 SubSequence 進行存儲和返回。

Collection

對 Sequence 進行進一步的完善,最重要的就是使其具有下標索引,使得元素能夠通過下標索引方式取到。Collection 是個有限的范圍,有開始索引和結束索引,所以 Collection 和 Sequence 的無限范圍是不一樣的。有了有限的范圍 Collection 就可以有 count 屬性進行計數了。

除了標準庫里的 String,Array,Dictionary 和 Set 外比如 Data 和 IndexSet 也由于遵循了 Collection 協議而獲得了標準庫了那些集合類型的能力。

map

在泛型的第一個例子里我們就看到了 map 的使用,我們看看 map 的定義:

func map<T>(transform: (Self.Generator.Element) -> T) rethrows -> [T]

這里 (Self.Generator.Element) -> T 就是 map 閉包的定義,Self.Generator.Element 就是當前元素的類型。

flatmap

二維數組經過 flatmap 會降維到一維,還能過濾掉 nil 值。下面看看 Swift 源碼(swift/stdlib/public/core/SequenceAlgorithms.swift.gyb)中 flatmap 的實現:

//===----------------------------------------------------------------------===//
// flatMap()
//===----------------------------------------------------------------------===//

extension Sequence {
  /// Returns an array containing the concatenated results of calling the
  /// given transformation with each element of this sequence.
  ///
  /// Use this method to receive a single-level collection when your
  /// transformation produces a sequence or collection for each element.
  ///
  /// In this example, note the difference in the result of using `map` and
  /// `flatMap` with a transformation that returns an array.
  ///
  ///     let numbers = [1, 2, 3, 4]
  ///
  ///     let mapped = numbers.map { Array(count: $0, repeatedValue: $0) }
  ///     // [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
  ///
  ///     let flatMapped = numbers.flatMap { Array(count: $0, repeatedValue: $0) }
  ///     // [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
  ///
  /// In fact, `s.flatMap(transform)`  is equivalent to
  /// `Array(s.map(transform).joined())`.
  ///
  /// - Parameter transform: A closure that accepts an element of this
  ///   sequence as its argument and returns a sequence or collection.
  /// - Returns: The resulting flattened array.
  ///
  /// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
  ///   and *n* is the length of the result.
  /// - SeeAlso: `joined()`, `map(_:)`
  public func flatMap<SegmentOfResult : Sequence>(
    _ transform: (${GElement}) throws -> SegmentOfResult
  ) rethrows -> [SegmentOfResult.${GElement}] {
    var result: [SegmentOfResult.${GElement}] = []
    for element in self {
      result.append(contentsOf: try transform(element))
    }
    return result
  }
}

extension Sequence {
  /// Returns an array containing the non-`nil` results of calling the given
  /// transformation with each element of this sequence.
  ///
  /// Use this method to receive an array of nonoptional values when your
  /// transformation produces an optional value.
  ///
  /// In this example, note the difference in the result of using `map` and
  /// `flatMap` with a transformation that returns an optional `Int` value.
  ///
  ///     let possibleNumbers = ["1", "2", "three", "http:///4///", "5"]
  ///
  ///     let mapped: [Int?] = possibleNumbers.map { str in Int(str) }
  ///     // [1, 2, nil, nil, 5]
  ///
  ///     let flatMapped: [Int] = possibleNumbers.flatMap { str in Int(str) }
  ///     // [1, 2, 5]
  ///
  /// - Parameter transform: A closure that accepts an element of this
  ///   sequence as its argument and returns an optional value.
  /// - Returns: An array of the non-`nil` results of calling `transform`
  ///   with each element of the sequence.
  ///
  /// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
  ///   and *n* is the length of the result.
  public func flatMap<ElementOfResult>(
    _ transform: (${GElement}) throws -> ElementOfResult?
  ) rethrows -> [ElementOfResult] {
    var result: [ElementOfResult] = []
    for element in self {
      if let newElement = try transform(element) {
        result.append(newElement)
      }
    }
    return result
  }
}

從代碼中可以看出打平的原理是將集合中所有元素都添加到另外一個集合里。在第二個 extension 里通過 if let 語句會擋住那些解包不成功的元素。

Reduce

Reduce 是編程語言語義學里的歸約語義學,也叫累加器。下面同樣可以看看 Swift 源碼里對其的實現:

//===----------------------------------------------------------------------===//
// reduce()
//===----------------------------------------------------------------------===//

extension Sequence {
  /// Returns the result of combining the elements of the sequence using the
  /// given closure.
  ///
  /// Use the `reduce(_:_:)` method to produce a single value from the elements
  /// of an entire sequence. For example, you can use this method on an array
  /// of numbers to find their sum or product.
  ///
  /// The `nextPartialResult` closure is called sequentially with an
  /// accumulating value initialized to `initialResult` and each element of
  /// the sequence. This example shows how to find the sum of an array of
  /// numbers.
  ///
  ///     let numbers = [1, 2, 3, 4]
  ///     let numberSum = numbers.reduce(0, { x, y in
  ///         x + y
  ///     })
  ///     // numberSum == 10
  ///
  /// When `numbers.reduce(_:_:)` is called, the following steps occur:
  ///
  /// 1. The `nextPartialResult` closure is called with `initialResult`---`0`
  ///    in this case---and the first element of `numbers`, returning the sum:
  ///    `1`.
  /// 2. The closure is called again repeatedly with the previous call's return
  ///    value and each element of the sequence.
  /// 3. When the sequence is exhausted, the last value returned from the
  ///    closure is returned to the caller.
  ///
  /// If the sequence has no elements, `nextPartialResult` is never executed
  /// and `initialResult` is the result of the call to `reduce(_:_:)`.
  ///
  /// - Parameters:
  ///   - initialResult: The value to use as the initial accumulating value.
  ///     `initialResult` is passed to `nextPartialResult` the first time the
  ///     closure is executed.
  ///   - nextPartialResult: A closure that combines an accumulating value and
  ///     an element of the sequence into a new accumulating value, to be used
  ///     in the next call of the `nextPartialResult` closure or returned to
  ///     the caller.
  /// - Returns: The final accumulated value. If the sequence has no elements,
  ///   the result is `initialResult`.
  public func reduce<Result>(
    _ initialResult: Result,
    _ nextPartialResult:
      (_ partialResult: Result, ${GElement}) throws -> Result
  ) rethrows -> Result {
    var accumulator = initialResult
    for element in self {
      accumulator = try nextPartialResult(accumulator, element)
    }
    return accumulator
  }
}

可以看到里面會通過 initialResult 來記錄前面的返回結果和當前元素進行在閉包里的操作。

Array

看看數組的基本用法

//創建數組
var nums = [Int]() //創建空數組
var mArray = nums + [2,3,5] + [5,9]//合并多個有相同類型元素數組的值
var animals: [String] = ["dragon", "cat", "mice", "dog"]

//添加數組
animals.append("bird")
animals += ["ant"]

//獲取和改變數組
var firstItem = mArray[0]
animals[0] = "red dragon"
animals[2...4] = ["black dragon", "white dragon"] //使用下標改變多個元素
animals.insert("chinese dragon", at: 0) //在索引值之前添加元素
let mapleSyrup = animals.remove(at: 0) //移除數組中的一個元素
let apples = animals.removeLast() //移除最后一個元素

////數組遍歷
for animal in animals {
    print(animal)
}
for (index, animal) in animals.enumerated() {
    print("animal \(String(index + 1)): \(animal)")
}
/*
 animal 1: red dragon
 animal 2: cat
 animal 3: black dragon
 animal 4: white dragon
 */

弱引用的 Swift 數組

Swift 里的數組默認會強引用里面的元素,但是有時候可能希望能夠弱引用,那么就可以使用 NSPointerArray。它在初始化的時候可以決定是用弱引用方式還是強引用方式。

let strongArr = NSPointerArray.strongObjects() // 強引用
let weakArr = NSPointerArray.weakObjects() // Maintains weak references

Dictionary 的要想用弱引用可以使用 NSMapTable,Set 對應的是 NSHashTable。

Dictionary

看看基本用法:

//創建 Dictionary
var strs = [Int: String]()
var colors: [String: String] = ["red": "#e83f45", "yellow": "#ffe651"]
strs[16] = "sixteen"

//updateValue 這個方法會返回更新前的值
if let oldValue = colors.updateValue("#e83f47", forKey: "red") {
    print("The old value for DUB was \(oldValue).")
}

//遍歷
for (color, value) in colors {
    print("\(color): \(value)")
}

//map
let newColorValues = colors.map { "hex:\($0.value)" }
print("\(newColorValues)")

//mapValues 返回完整的新 Dictionary
let newColors = colors.mapValues { "hex:\($0)" }
print("\(newColors)")

協議式編程

Swift 被設計成單繼承,如果希望是多繼承就需要使用協議。協議還有個比較重要的作用就是通過 associatedtype 要求使用者遵守指定的泛型約束。

下面先看看傳統編程的開發模式:

class Dragon {
    
}
class BlackDragon: Dragon{
    func fire() {
        print("fire!!!")
    }
}

class WhiteDragon: Dragon {
    func fire() {
        print("fire!!!")
    }
}

BlackDragon().fire()
WhiteDragon().fire()

這個例子可以看出 fire() 就是重復代碼,那么首先想到的方法就是通過直接在基類里添加這個方法或者通過 extension 來對他們基類進行擴展:

extension Dragon {
    func fire() {
        print("fire!!!")
    }
}

這時我們希望加個方法讓 Dragon 能夠 fly:

extension Dragon {
    func fire() {
        print("fire!!!")
    }
    func fly() {
        print("fly~~~")
    }
}

這樣 BlackDragon 和 WhiteDragon 就都有這兩個能力了,如果我們設計出一個新的龍 YellowDragon 或者更多 Dragon 都沒有 fly 的能力,這時該如何。因為沒法多繼承,那么沒法拆成兩個基類,這樣必然就會出現重復代碼。但是有了協議這個問題就好解決了。具體實現如下:

protocol DragonFire {}
protocol DragonFly {}

extension DragonFire {
    func fire() {
        print("fire!!!")
    }
}
extension DragonFly {
    func fly() {
        print("fly~~~")
    }
}

class BlackDragon: DragonFire, DragonFly {}
class WhiteDragon: DragonFire, DragonFly {}
class YellowDragon: DragonFire {}
class PurpleDragon: DragonFire {}

BlackDragon().fire()
WhiteDragon().fire()
BlackDragon().fly()
YellowDragon().fire()

可以看到一來沒有了重復代碼,二來結構也清晰了很多而且更容易擴展,Dragon 的種類和能力的組合也更加方便和清晰。extension 使得協議有了實現默認方法的能力。

關于多繼承 Swift 是采用 Trait 的方式,其它語言 C++ 是直接支持多繼承的,方式是這個類會持有多個父類的實例。Java 的多繼承只繼承能做什么,怎么做還是要自己來。和 Trait 類似的解決方案是 Mixin,Ruby 就是用的這種元編程思想。

協議還可以繼承,還可以通過 & 來聚合,判斷一個類是否遵循了一個協議可以使用 is 關鍵字。

當然協議還可以作為類型,比如一個數組泛型元素指定為一個協議,那么這個數組里的元素只要遵循這個協議就可以了。

Swift 內存管理

內存分配

Heap

在 Heap 上內存分配的時候需要鎖定 Heap 上能夠容納存放對象的空閑塊,主要是為了線程安全,我們需要對這些進行鎖定和同步。

Heap 是完全二叉樹,即除最底層節點外都是填滿的,最底層節點填充是從左到右。Swift 的 Heap 是通過雙向鏈表實現。由于 Heap 是可以 retain 和 release 所以很容易分配空間就不連續了。采用鏈表的目的是希望能夠將內存塊連起來,在 release 時通過調整鏈表指針來整合空間。

在 retain 時不可避免需要遍歷 Heap,找到合適大小的內存塊,能優化的也只是記錄以前遍歷的情況減少一些遍歷。但是 Heap 是很大的,這樣每次遍歷還是很耗時,而且 release 為了能夠整合空間還需要判斷當前內存塊的前一塊和后面那塊是否為空閑等,如果空閑還需要遍歷鏈表查詢,所以最終的解決方式是雙向鏈表。只把空閑內存塊用指針連起來形成鏈表,這樣 retain 時可以減少遍歷,效率理論上可以提高一倍,在 release 時將多余空間插入到 Heap 開始的位置和先前移到前面的空間進行整合。

即使效率高了但是還是比不過 Stack,所以蘋果也將以前 OC 里的一些放在 Heap 里的類型改造成了值類型。

Stack

Stack 的結構很簡單,push 和 pop 就完事了,內存上只需要維護 Stack 末端的指針即可。由于它的簡單所以處理一些時效性不高,臨時的事情是非常合適的,所以可以把 Stack 看成是一個交換臨時數據的內存區域。在多線程上,由于 Stack 是線程獨有的,所以也不需要考慮線程安全相關問題。

內存對齊

Swift 也有內存對齊的概念

struct DragonFirePosition {
    var x:Int64 //8 Bytes
    var y:Int32 //4 Bytes
    //8 + 4
}
struct DragonHomePosition {
    var y:Int32 //4 Bytes + 對齊內存(4 Bytes)
    var x:Int64 //8 Bytes
    //4 + 4 + 8
}
let firePositionSize = MemoryLayout<DragonFirePosition>.size //12
let homePositionSize = MemoryLayout<DragonHomePosition>.size //16

Swift 派發機制

派發目的是讓 CPU 知道被調用的函數在哪里。Swift 語言是支持編譯型語言的直接派發,函數表派發和消息機制派發三種派發方式的,下面分別對這三種派發方式說明下。

直接派發

C++ 默認使用的是直接派發,加上 virtual 修飾符可以改成函數表派發。直接派發是最快的,原因是調用指令會少,還可以通過編譯器進行比如內聯等方式的優化。缺點是由于缺少動態性而不支持繼承。

struct DragonFirePosition {
    var x:Int64
    var y:Int32
    func land() {}
}

func DragonWillFire(_ position:DragonFirePosition) {
    position.land()
}
let position = DragonFirePosition(x: 342, y: 213)
DragonWillFire(position)

編譯 inline 后 DragonWillFire(DragonFirePosition(x: 342, y: 213)) 會直接跳到方法實現的地方,結果就變成 position.land()。

函數表派發

Java 默認就是使用的函數表派發,通過 final 修飾符改成直接派發。函數表派發是有動態性的,在 Swift 里函數表叫 witness table,大部分語言叫 virtual table。一個類里會用數組來存儲里面的函數指針,override 父類的函數會替代以前的函數,子類添加的函數會被加到這個數組里。舉個例子:

class Fish {
    func swim() {}
    func eat() {
        //normal eat
    }
}

class FlyingFish: Fish {
    override func eat() {
        //flying fish eat
    }
    func fly() {}
}

編譯器會給 Fish 類和 FlyingFish 類分別創建 witness table。在 Fish 的函數表里有 swim 和 eat 函數,在 FlyingFish 函數表里有父類 Fish 的 swim,覆蓋了父類的 eat 和新增加的函數 fly。

一個函數被調用時會先去讀取對象的函數表,再根據類的地址加上該的函數的偏移量得到函數地址,然后跳到那個地址上去。從編譯后的字節碼這方面來看就是兩次讀取一次跳轉,比直接派發還是慢了些。

消息機制派發

這種機制是在運行時可以改變函數的行為,KVO 和 CoreData 都是這種機制的運用。OC 默認就是使用的消息機制派發,使用 C 來直接派發獲取高性能。Swift 可以通過 dynamic 修飾來支持消息機制派發。

當一個消息被派發,運行時就會按照繼承關系向上查找被調用的函數。但是這樣效率不高,所以需要通過緩存來提高效率,這樣查找性能就能和函數派發差不多了。

具體派發

聲明

值類型都會采用直接派發。無論是 class 還是協議 的 extension 也都是直接派發。class 和協議是函數表派發。

指定派發方式

  • final:讓類里的函數使用直接派發,這樣該函數將會沒有動態性,運行時也沒法取到這個函數。
  • dynamic:可以讓類里的函數使用消息機制派發,可以讓 extension 里的函數被 override。

派發優化

Swift 會在這上面做優化,比如一個函數沒有 override,Swift 就可能會使用直接派發的方式,所以如果屬性綁定了 KVO 它的 getter 和 setter 方法可能會被優化成直接派發而導致 KVO 的失效,所以記得加上 dynamic 的修飾來保證有效。后面 Swift 應該會在這個優化上去做更多的處理。

基本數據類型內存管理

通過 MemoryLayout 來看看基本數據類型的內存是占用多大

MemoryLayout<Int>.size      //8
MemoryLayout<Int16>.size    //2
MemoryLayout<Bool>.size     //1
MemoryLayout<Float>.size    //4
MemoryLayout<Double>.size   //8

Struct 內存管理

對于 Struct 在編譯中就能夠確定空間,也就不需要額外的空間給運行時用,運行過程調用時就是直接傳地址。

下面我們再看看 Struct 的 MemoryLayout

struct DragonFirePosition {
    var x:Int64 //8 Bytes
    var y:Int32 //4 Bytes
    //8 + 4
    func land() {}
}

MemoryLayout<DragonFirePosition>.size       //12
MemoryLayout<DragonFirePosition>.alignment  //8
MemoryLayout<DragonFirePosition>.stride     //16

alignment 可以看出是按照 8 Bytes 來對齊的,所以這里 struct 是用到了字節對齊,實際占用大小通過 stride 可以看出就是 8 * 2 為16。

如果把 var x:Int64 改成可選類型會增加 4個 Bytes,不過就這個 case 來說實際大小還是16,這個也是因為內存對齊的原因。

Class 內存管理

Class 本身是在 Stack 上分配的,在 Heap 上還需要保存 Class 的 Type 信息,這個 Type 信息里有函數表,在函數派發時就可以按照這個函數表進行派發了。繼承的話子類只要在自己的 Type 信息里記錄自己的信息即可。

協議類型內存管理

協議類型的內存模型是 Existential Container。先看下面例子

protocol DragonFire {}
extension DragonFire {
    func fire() {
        print("fire!!!")
    }
}

struct YellowDragon: DragonFire {
    let eyes = "blue"
    let teeth = 48
}

let classSize = MemoryLayout<YellowDragon>.size  //32
let protocolSize = MemoryLayout<DragonFire>.size //40

這個例子里看這個結構體即遵循了這個協議而且內容還比這個協議多,而協議的 size 還要大,這個是為啥呢?這個是由于 Existential Container 的前三個 word 是叫 Value buffer 用來存儲 inline 的值。第四個 word 是 Value Witness Table,存儲值的各種操作比如 allocate,copy,destruct 和 deallocate 等。第五個 word 是 Protocol Witness Table 是存儲協議的函數。

泛型的內存管理

泛型采用的和 Existential Container 原理類似。Value Witness Table 和 Protocol Witness Table 會作為隱形的參數傳遞到泛型方法里。不過經過編譯器的層層 inline 優化,最終類型都會被推導出來也就不再需要 Existential Container 這一套了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容