前言
寫這篇文章主要是為了給組內(nèi)要做的分享準(zhǔn)備內(nèi)容。這段時(shí)間幾個(gè)項(xiàng)目都用到 Swift,在上次 GIAC 大會(huì)上就被問(wèn)到為什么要用 Swift,正好這個(gè)主題可以聊聊 Swift 的哪些特性吸引了我。
泛型
先來(lái)個(gè)例子看下泛型是解決什么問(wèn)題的。
let nations = ["中國(guó)", "美國(guó)", "日本"]
func showNations(arr : [String]) {
arr.map { str in
print("\(str)")
}
}
我們先定一個(gè)字符串?dāng)?shù)組,然后把里面的字符串打印出來(lái)。這里的 map 寫法還可以優(yōu)化下:
arr.map { print("\($0)") }
那么還能做什么優(yōu)化呢。將 showNations 的入?yún)?shù)組泛型以支持多類型,比如 [int],[double] 等。
func showArray<T>(arr: [T]) {
arr.map { print("\($0)") }
}
可以看出泛型能夠很方便的將不同類型數(shù)據(jù)進(jìn)行相同操作的邏輯歸并在一起。
類型約束
先看下我的 HTN 項(xiàng)目里狀態(tài)機(jī)的 Transition 結(jié)構(gòu)體的定義
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 可以是不同類型的數(shù)據(jù),可以是枚舉,字符串或者整數(shù)等,定義 S 和 E 兩個(gè)不同的泛型可以讓狀態(tài)和事件類型不相同,這樣接口會(huì)更加的靈活,更容易適配更多的項(xiàng)目。
大家會(huì)注意到 S 和 E 的冒號(hào)后面還有個(gè) Hashable 協(xié)議,這就是要求它們符合這個(gè)協(xié)議的類型約束。使用協(xié)議的話可以使得這兩個(gè)類型更加的規(guī)范和易于擴(kuò)展。
Swift 的基本類型 String,Int,Double 和 Bool 等都是遵循 Hashable 的,還有無(wú)關(guān)聯(lián)值的枚舉也是的。Hashable 提供了一個(gè) hashValue 方法用在判斷遵循協(xié)議對(duì)象是否相等時(shí)用。
Hashable 協(xié)議同時(shí)也是遵守 Equatable 協(xié)議,通過(guò)實(shí)現(xiàn) == 運(yùn)算符來(lái)確定自定義的類或結(jié)構(gòu)是否相同。
關(guān)聯(lián)類型
在協(xié)議里定義的關(guān)聯(lián)類型也可以用泛型來(lái)處理。比如我們先定義一個(gè)協(xié)議
protocol HTNState {
associatedtype StateType
func add(_ item: StateType)
}
采用非泛型的實(shí)現(xiàn)如下:
struct states: HTNState {
typealias StateType = Int
func add(_ item: Int) {
//...
}
}
采用泛型遵循協(xié)議可以按照下面方式來(lái)寫:
struct states<T>: HTNState {
func add(_ item: T) {
//...
}
}
這樣關(guān)聯(lián)類型也能夠享受泛型的好處了。
類型擦除
但是在使用關(guān)聯(lián)類型的時(shí)候需要注意當(dāng)聲明一個(gè)使用了關(guān)聯(lián)屬性的協(xié)議作為屬性時(shí),比如下面的代碼:
class stateDelegate<T> {
var state: T
var delegate: HTNState
}
先會(huì)提示 no initializers 的錯(cuò)誤,接著會(huì)提示 error: protocol 'HTNState' can only be used as a generic constraint because it has Self or associated type requirements 。意思是 HTNState 協(xié)議只能作為泛型約束來(lái)用,因?yàn)樗锩姘匦璧?self 或者關(guān)聯(lián)類型。
那么該如何處理呢?這里需要通過(guò)類型擦除來(lái)解決,主要思路就是加個(gè)中間層在代碼中讓這個(gè)抽象的類型具體化。實(shí)際上在 Swift 的標(biāo)準(zhǔn)庫(kù)里就有類型擦除很好的運(yùn)用,比如 AnySequence 的協(xié)議。
Where 語(yǔ)句
函數(shù),擴(kuò)展和關(guān)聯(lián)類型都可以使用 where 語(yǔ)句。where 語(yǔ)句是對(duì)泛型在應(yīng)用時(shí)的一種約束。比如:
func stateFilter<FromState:HTNState, ToState:HTNState>(_ from:FromState, _ to:ToState) where FromState.StateType == ToState.StateType {
//...
}
這個(gè)函數(shù)就要求他們的 StateType 具有相同類型。
泛型和 Any 類型
這兩個(gè)類型看起來(lái)很相似,但是一定要小心兩者的區(qū)別。他們區(qū)別在于 Any 類型會(huì)避開類型的檢查,所以盡量少用最好不用。泛型一方面很靈活一方面也很安全,下面舉個(gè)例子感受下兩者的區(qū)別:
func add<T>(_ input: T) -> T {
//...
return input;
}
func anyAdd(_ input: Any) -> Any {
//...
return input;
}
這兩個(gè)函數(shù)都是可以允許任意類型的 input 參數(shù),不同在于返回的類型在 anyAdd 函數(shù)里是可以和入?yún)⒉灰粯拥模@樣就會(huì)失控,在后續(xù)的操作中容易出錯(cuò)。
集合
基本概念
先來(lái)了解下集合的基本概念,首先集合是泛型的比如:
let stateArray: Array<String> = ["工作","吃飯","玩游戲","睡覺(jué)"]
集合它需要先有個(gè)遍歷的功能,通過(guò) GeneratorType 協(xié)議,可以不關(guān)注具體元素類型只要不斷的用迭代器調(diào) next 就可以得到全部元素。但是使用迭代器沒(méi)法進(jìn)行多次的遍歷,這時(shí)就需要使用 Sequence 來(lái)解決這個(gè)問(wèn)題。像集合的 forEach,elementsEqual,contains,minElement,maxElement,map,flatMap,filter,reduce 等功能都是因?yàn)橛辛?Sequence 的多次遍歷。
最后 Collection 概念是因?yàn)?Sequence 無(wú)法確定集合里的位置而在 Sequence 的基礎(chǔ)上實(shí)現(xiàn)了 Indexable 協(xié)議。有了 Collection 就可以確定元素的位置,包括開始位置和結(jié)束位置,這樣就能夠確定哪些元素是已經(jīng)訪問(wèn)過(guò)的,從而避免多次訪問(wèn)同一個(gè)元素。還能夠通過(guò)一個(gè)給定的位置直接找到那個(gè)位置的元素。
以上描述如下圖:
迭代器
Swift 里有個(gè)簡(jiǎn)單的 AnyIterator<Element> 結(jié)構(gòu)體
struct AnyIterator<Element>: IteratorProtocol {
init(_ body: @escaping () -> Element?)
//...
}
AnyIterator 實(shí)現(xiàn)了 IteratorProtocol 和 Sequence 協(xié)議。通過(guò)下面的例子我們來(lái)看看如何使用 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())))
首先是定義個(gè)遵循了 IteratorProtocol 并實(shí)現(xiàn)了 next 函數(shù)的類。再實(shí)現(xiàn)一個(gè) AnyIterator 的迭代器方法,這樣通過(guò)這個(gè)方法的調(diào)用就可以不斷的去找符合的元素了。
這里有個(gè)對(duì) where 語(yǔ)句的運(yùn)用,where I.Element == Int。如果把這句改成 where I.Element == String 會(huì)出現(xiàn)下面的錯(cuò)誤提示
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
^
編譯器會(huì)在代碼檢查階段通過(guò)代碼跟蹤就發(fā)現(xiàn)類型不匹配的安全隱患,這里不得不對(duì) Swift 的設(shè)計(jì)點(diǎn)個(gè)贊先
Sequence
上面的迭代器只會(huì)以單次觸發(fā)的方式反復(fù)計(jì)算下個(gè)元素,但是對(duì)于希望能夠重新查找或重新生成已生成的元素,這樣還需要有個(gè)新的迭代器和一個(gè)子 Sequence。在 Sequence 協(xié)議里可以看到這樣的定義:
public protocol Sequence {
//Element 表示序列元素的類型
associatedtype Element where Self.Element == Self.Iterator.Element
//迭代接口類型
associatedtype Iterator : IteratorProtocol
//子 Sequence 類型
associatedtype SubSequence
//返回 Sequence 元素的迭代器
public func makeIterator() -> Self.Iterator
//...
}
重新查找靠的是這個(gè)新的迭代器,而對(duì)于切片這樣的會(huì)重新生成新 Sequence 的操作就需要 SubSequence 進(jìn)行存儲(chǔ)和返回。
Collection
對(duì) Sequence 進(jìn)行進(jìn)一步的完善,最重要的就是使其具有下標(biāo)索引,使得元素能夠通過(guò)下標(biāo)索引方式取到。Collection 是個(gè)有限的范圍,有開始索引和結(jié)束索引,所以 Collection 和 Sequence 的無(wú)限范圍是不一樣的。有了有限的范圍 Collection 就可以有 count 屬性進(jìn)行計(jì)數(shù)了。
除了標(biāo)準(zhǔn)庫(kù)里的 String,Array,Dictionary 和 Set 外比如 Data 和 IndexSet 也由于遵循了 Collection 協(xié)議而獲得了標(biāo)準(zhǔn)庫(kù)了那些集合類型的能力。
map
在泛型的第一個(gè)例子里我們就看到了 map 的使用,我們看看 map 的定義:
func map<T>(transform: (Self.Generator.Element) -> T) rethrows -> [T]
這里 (Self.Generator.Element) -> T 就是 map 閉包的定義,Self.Generator.Element 就是當(dāng)前元素的類型。
flatmap
二維數(shù)組經(jīng)過(guò) flatmap 會(huì)降維到一維,還能過(guò)濾掉 nil 值。下面看看 Swift 源碼(swift/stdlib/public/core/SequenceAlgorithms.swift.gyb)中 flatmap 的實(shí)現(xiàn):
//===----------------------------------------------------------------------===//
// 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
}
}
從代碼中可以看出打平的原理是將集合中所有元素都添加到另外一個(gè)集合里。在第二個(gè) extension 里通過(guò) if let 語(yǔ)句會(huì)擋住那些解包不成功的元素。
Reduce
Reduce 是編程語(yǔ)言語(yǔ)義學(xué)里的歸約語(yǔ)義學(xué),也叫累加器。下面同樣可以看看 Swift 源碼里對(duì)其的實(shí)現(xiàn):
//===----------------------------------------------------------------------===//
// 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
}
}
可以看到里面會(huì)通過(guò) initialResult 來(lái)記錄前面的返回結(jié)果和當(dāng)前元素進(jìn)行在閉包里的操作。
Array
看看數(shù)組的基本用法
//創(chuàng)建數(shù)組
var nums = [Int]() //創(chuàng)建空數(shù)組
var mArray = nums + [2,3,5] + [5,9]//合并多個(gè)有相同類型元素?cái)?shù)組的值
var animals: [String] = ["dragon", "cat", "mice", "dog"]
//添加數(shù)組
animals.append("bird")
animals += ["ant"]
//獲取和改變數(shù)組
var firstItem = mArray[0]
animals[0] = "red dragon"
animals[2...4] = ["black dragon", "white dragon"] //使用下標(biāo)改變多個(gè)元素
animals.insert("chinese dragon", at: 0) //在索引值之前添加元素
let mapleSyrup = animals.remove(at: 0) //移除數(shù)組中的一個(gè)元素
let apples = animals.removeLast() //移除最后一個(gè)元素
////數(shù)組遍歷
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 數(shù)組
Swift 里的數(shù)組默認(rèn)會(huì)強(qiáng)引用里面的元素,但是有時(shí)候可能希望能夠弱引用,那么就可以使用 NSPointerArray。它在初始化的時(shí)候可以決定是用弱引用方式還是強(qiáng)引用方式。
let strongArr = NSPointerArray.strongObjects() // 強(qiáng)引用
let weakArr = NSPointerArray.weakObjects() // Maintains weak references
Dictionary 的要想用弱引用可以使用 NSMapTable,Set 對(duì)應(yīng)的是 NSHashTable。
Dictionary
看看基本用法:
//創(chuàng)建 Dictionary
var strs = [Int: String]()
var colors: [String: String] = ["red": "#e83f45", "yellow": "#ffe651"]
strs[16] = "sixteen"
//updateValue 這個(gè)方法會(huì)返回更新前的值
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)")
協(xié)議式編程
Swift 被設(shè)計(jì)成單繼承,如果希望是多繼承就需要使用協(xié)議。協(xié)議還有個(gè)比較重要的作用就是通過(guò) associatedtype 要求使用者遵守指定的泛型約束。
下面先看看傳統(tǒng)編程的開發(fā)模式:
class Dragon {
}
class BlackDragon: Dragon{
func fire() {
print("fire!!!")
}
}
class WhiteDragon: Dragon {
func fire() {
print("fire!!!")
}
}
BlackDragon().fire()
WhiteDragon().fire()
這個(gè)例子可以看出 fire() 就是重復(fù)代碼,那么首先想到的方法就是通過(guò)直接在基類里添加這個(gè)方法或者通過(guò) extension 來(lái)對(duì)他們基類進(jìn)行擴(kuò)展:
extension Dragon {
func fire() {
print("fire!!!")
}
}
這時(shí)我們希望加個(gè)方法讓 Dragon 能夠 fly:
extension Dragon {
func fire() {
print("fire!!!")
}
func fly() {
print("fly~~~")
}
}
這樣 BlackDragon 和 WhiteDragon 就都有這兩個(gè)能力了,如果我們?cè)O(shè)計(jì)出一個(gè)新的龍 YellowDragon 或者更多 Dragon 都沒(méi)有 fly 的能力,這時(shí)該如何。因?yàn)闆](méi)法多繼承,那么沒(méi)法拆成兩個(gè)基類,這樣必然就會(huì)出現(xiàn)重復(fù)代碼。但是有了協(xié)議這個(gè)問(wèn)題就好解決了。具體實(shí)現(xiàn)如下:
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()
可以看到一來(lái)沒(méi)有了重復(fù)代碼,二來(lái)結(jié)構(gòu)也清晰了很多而且更容易擴(kuò)展,Dragon 的種類和能力的組合也更加方便和清晰。extension 使得協(xié)議有了實(shí)現(xiàn)默認(rèn)方法的能力。
關(guān)于多繼承 Swift 是采用 Trait 的方式,其它語(yǔ)言 C++ 是直接支持多繼承的,方式是這個(gè)類會(huì)持有多個(gè)父類的實(shí)例。Java 的多繼承只繼承能做什么,怎么做還是要自己來(lái)。和 Trait 類似的解決方案是 Mixin,Ruby 就是用的這種元編程思想。
協(xié)議還可以繼承,還可以通過(guò) & 來(lái)聚合,判斷一個(gè)類是否遵循了一個(gè)協(xié)議可以使用 is 關(guān)鍵字。
當(dāng)然協(xié)議還可以作為類型,比如一個(gè)數(shù)組泛型元素指定為一個(gè)協(xié)議,那么這個(gè)數(shù)組里的元素只要遵循這個(gè)協(xié)議就可以了。
Swift 內(nèi)存管理
內(nèi)存分配
Heap
在 Heap 上內(nèi)存分配的時(shí)候需要鎖定 Heap 上能夠容納存放對(duì)象的空閑塊,主要是為了線程安全,我們需要對(duì)這些進(jìn)行鎖定和同步。
Heap 是完全二叉樹,即除最底層節(jié)點(diǎn)外都是填滿的,最底層節(jié)點(diǎn)填充是從左到右。Swift 的 Heap 是通過(guò)雙向鏈表實(shí)現(xiàn)。由于 Heap 是可以 retain 和 release 所以很容易分配空間就不連續(xù)了。采用鏈表的目的是希望能夠?qū)?nèi)存塊連起來(lái),在 release 時(shí)通過(guò)調(diào)整鏈表指針來(lái)整合空間。
在 retain 時(shí)不可避免需要遍歷 Heap,找到合適大小的內(nèi)存塊,能優(yōu)化的也只是記錄以前遍歷的情況減少一些遍歷。但是 Heap 是很大的,這樣每次遍歷還是很耗時(shí),而且 release 為了能夠整合空間還需要判斷當(dāng)前內(nèi)存塊的前一塊和后面那塊是否為空閑等,如果空閑還需要遍歷鏈表查詢,所以最終的解決方式是雙向鏈表。只把空閑內(nèi)存塊用指針連起來(lái)形成鏈表,這樣 retain 時(shí)可以減少遍歷,效率理論上可以提高一倍,在 release 時(shí)將多余空間插入到 Heap 開始的位置和先前移到前面的空間進(jìn)行整合。
即使效率高了但是還是比不過(guò) Stack,所以蘋果也將以前 OC 里的一些放在 Heap 里的類型改造成了值類型。
Stack
Stack 的結(jié)構(gòu)很簡(jiǎn)單,push 和 pop 就完事了,內(nèi)存上只需要維護(hù) Stack 末端的指針即可。由于它的簡(jiǎn)單所以處理一些時(shí)效性不高,臨時(shí)的事情是非常合適的,所以可以把 Stack 看成是一個(gè)交換臨時(shí)數(shù)據(jù)的內(nèi)存區(qū)域。在多線程上,由于 Stack 是線程獨(dú)有的,所以也不需要考慮線程安全相關(guān)問(wèn)題。
內(nèi)存對(duì)齊
Swift 也有內(nèi)存對(duì)齊的概念
struct DragonFirePosition {
var x:Int64 //8 Bytes
var y:Int32 //4 Bytes
//8 + 4
}
struct DragonHomePosition {
var y:Int32 //4 Bytes + 對(duì)齊內(nèi)存(4 Bytes)
var x:Int64 //8 Bytes
//4 + 4 + 8
}
let firePositionSize = MemoryLayout<DragonFirePosition>.size //12
let homePositionSize = MemoryLayout<DragonHomePosition>.size //16
Swift 派發(fā)機(jī)制
派發(fā)目的是讓 CPU 知道被調(diào)用的函數(shù)在哪里。Swift 語(yǔ)言是支持編譯型語(yǔ)言的直接派發(fā),函數(shù)表派發(fā)和消息機(jī)制派發(fā)三種派發(fā)方式的,下面分別對(duì)這三種派發(fā)方式說(shuō)明下。
直接派發(fā)
C++ 默認(rèn)使用的是直接派發(fā),加上 virtual 修飾符可以改成函數(shù)表派發(fā)。直接派發(fā)是最快的,原因是調(diào)用指令會(huì)少,還可以通過(guò)編譯器進(jìn)行比如內(nèi)聯(lián)等方式的優(yōu)化。缺點(diǎn)是由于缺少動(dòng)態(tài)性而不支持繼承。
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)) 會(huì)直接跳到方法實(shí)現(xiàn)的地方,結(jié)果就變成 position.land()。
函數(shù)表派發(fā)
Java 默認(rèn)就是使用的函數(shù)表派發(fā),通過(guò) final 修飾符改成直接派發(fā)。函數(shù)表派發(fā)是有動(dòng)態(tài)性的,在 Swift 里函數(shù)表叫 witness table,大部分語(yǔ)言叫 virtual table。一個(gè)類里會(huì)用數(shù)組來(lái)存儲(chǔ)里面的函數(shù)指針,override 父類的函數(shù)會(huì)替代以前的函數(shù),子類添加的函數(shù)會(huì)被加到這個(gè)數(shù)組里。舉個(gè)例子:
class Fish {
func swim() {}
func eat() {
//normal eat
}
}
class FlyingFish: Fish {
override func eat() {
//flying fish eat
}
func fly() {}
}
編譯器會(huì)給 Fish 類和 FlyingFish 類分別創(chuàng)建 witness table。在 Fish 的函數(shù)表里有 swim 和 eat 函數(shù),在 FlyingFish 函數(shù)表里有父類 Fish 的 swim,覆蓋了父類的 eat 和新增加的函數(shù) fly。
一個(gè)函數(shù)被調(diào)用時(shí)會(huì)先去讀取對(duì)象的函數(shù)表,再根據(jù)類的地址加上該的函數(shù)的偏移量得到函數(shù)地址,然后跳到那個(gè)地址上去。從編譯后的字節(jié)碼這方面來(lái)看就是兩次讀取一次跳轉(zhuǎn),比直接派發(fā)還是慢了些。
消息機(jī)制派發(fā)
這種機(jī)制是在運(yùn)行時(shí)可以改變函數(shù)的行為,KVO 和 CoreData 都是這種機(jī)制的運(yùn)用。OC 默認(rèn)就是使用的消息機(jī)制派發(fā),使用 C 來(lái)直接派發(fā)獲取高性能。Swift 可以通過(guò) dynamic 修飾來(lái)支持消息機(jī)制派發(fā)。
當(dāng)一個(gè)消息被派發(fā),運(yùn)行時(shí)就會(huì)按照繼承關(guān)系向上查找被調(diào)用的函數(shù)。但是這樣效率不高,所以需要通過(guò)緩存來(lái)提高效率,這樣查找性能就能和函數(shù)派發(fā)差不多了。
具體派發(fā)
聲明
值類型都會(huì)采用直接派發(fā)。無(wú)論是 class 還是協(xié)議 的 extension 也都是直接派發(fā)。class 和協(xié)議是函數(shù)表派發(fā)。
指定派發(fā)方式
- final:讓類里的函數(shù)使用直接派發(fā),這樣該函數(shù)將會(huì)沒(méi)有動(dòng)態(tài)性,運(yùn)行時(shí)也沒(méi)法取到這個(gè)函數(shù)。
- dynamic:可以讓類里的函數(shù)使用消息機(jī)制派發(fā),可以讓 extension 里的函數(shù)被 override。
派發(fā)優(yōu)化
Swift 會(huì)在這上面做優(yōu)化,比如一個(gè)函數(shù)沒(méi)有 override,Swift 就可能會(huì)使用直接派發(fā)的方式,所以如果屬性綁定了 KVO 它的 getter 和 setter 方法可能會(huì)被優(yōu)化成直接派發(fā)而導(dǎo)致 KVO 的失效,所以記得加上 dynamic 的修飾來(lái)保證有效。后面 Swift 應(yīng)該會(huì)在這個(gè)優(yōu)化上去做更多的處理。
基本數(shù)據(jù)類型內(nèi)存管理
通過(guò) MemoryLayout 來(lái)看看基本數(shù)據(jù)類型的內(nèi)存是占用多大
MemoryLayout<Int>.size //8
MemoryLayout<Int16>.size //2
MemoryLayout<Bool>.size //1
MemoryLayout<Float>.size //4
MemoryLayout<Double>.size //8
Struct 內(nèi)存管理
對(duì)于 Struct 在編譯中就能夠確定空間,也就不需要額外的空間給運(yùn)行時(shí)用,運(yùn)行過(guò)程調(diào)用時(shí)就是直接傳地址。
下面我們?cè)倏纯?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 來(lái)對(duì)齊的,所以這里 struct 是用到了字節(jié)對(duì)齊,實(shí)際占用大小通過(guò) stride 可以看出就是 8 * 2 為16。
如果把 var x:Int64 改成可選類型會(huì)增加 4個(gè) Bytes,不過(guò)就這個(gè) case 來(lái)說(shuō)實(shí)際大小還是16,這個(gè)也是因?yàn)閮?nèi)存對(duì)齊的原因。
Class 內(nèi)存管理
Class 本身是在 Stack 上分配的,在 Heap 上還需要保存 Class 的 Type 信息,這個(gè) Type 信息里有函數(shù)表,在函數(shù)派發(fā)時(shí)就可以按照這個(gè)函數(shù)表進(jìn)行派發(fā)了。繼承的話子類只要在自己的 Type 信息里記錄自己的信息即可。
協(xié)議類型內(nèi)存管理
協(xié)議類型的內(nèi)存模型是 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
這個(gè)例子里看這個(gè)結(jié)構(gòu)體即遵循了這個(gè)協(xié)議而且內(nèi)容還比這個(gè)協(xié)議多,而協(xié)議的 size 還要大,這個(gè)是為啥呢?這個(gè)是由于 Existential Container 的前三個(gè) word 是叫 Value buffer 用來(lái)存儲(chǔ) inline 的值。第四個(gè) word 是 Value Witness Table,存儲(chǔ)值的各種操作比如 allocate,copy,destruct 和 deallocate 等。第五個(gè) word 是 Protocol Witness Table 是存儲(chǔ)協(xié)議的函數(shù)。
泛型的內(nèi)存管理
泛型采用的和 Existential Container 原理類似。Value Witness Table 和 Protocol Witness Table 會(huì)作為隱形的參數(shù)傳遞到泛型方法里。不過(guò)經(jīng)過(guò)編譯器的層層 inline 優(yōu)化,最終類型都會(huì)被推導(dǎo)出來(lái)也就不再需要 Existential Container 這一套了。