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

前言

寫這篇文章主要是爲了給組內要作的分享準備內容。這段時間幾個項目都用到 Swift,在上次 GIAC 大會上就被問到爲何要用 Swift,正好這個主題能夠聊聊 Swift 的哪些特性吸引了我。編程

泛型

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

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

咱們先定一個字符串數組,而後把裏面的字符串打印出來。這裏的 map 寫法還能夠優化下:api

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 兩個不一樣的泛型可讓狀態和事件類型不相同,這樣接口會更加的靈活,更容易適配更多的項目。bash

你們會注意到 S 和 E 的冒號後面還有個 Hashable 協議,這就是要求它們符合這個協議的類型約束。使用協議的話可使得這兩個類型更加的規範和易於擴展。多線程

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

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

關聯類型

在協議裏定義的關聯類型也能夠用泛型來處理。好比咱們先定義一個協議

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 就能夠肯定元素的位置,包括開始位置和結束位置,這樣就可以肯定哪些元素是已經訪問過的,從而避免屢次訪問同一個元素。還可以經過一個給定的位置直接找到那個位置的元素。

以上描述以下圖:

迭代器

Swift 裏有個簡單的 AnyIterator 結構體

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", "///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
複製代碼

能夠看出來協議要比類大。這個是因爲 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 這一套了。

相關文章
相關標籤/搜索