在“幽靈架構”Demo中我把兩個資料模型聲明成了Struct,蘋果WWDC2015的414号視訊講解了非常多關于Struct的優勢,其實也是所有值類型的優勢。首先Swift标準庫中絕大部分是值類型的,值類型的值傳遞是通過copy的,而作為一門靜态語言,Swift要求所有的對象都有明确的類型,明确的類型代表了固定的記憶體配置設定,而414号視訊也指出在記憶體中進行定長對象的copy是時間常數的,也就所謂的“cheap”。另外如果值類型的對象中包含引用類型的屬性的話,會破壞值類型的特性,出現共享(詳情請參考414号視訊),是以在Swift中,對于包含引用類型屬性的值類型對象間的Copy使用了Copy - on - Write這項技術,最基本的示例如下:
把引用類型的屬性聲明成private屬性,然後向外界暴露兩個公開的計算屬性,供讀取的計算屬性傳回私有屬性本身,供寫入的計算屬性傳回一個copy後的複本。這個版本的代碼的問題是雖然保證了整個struct的值類型特性,但是每次寫入都需要copy性能并不高,是以在Copy - on - Write中的pathForWriting加入了一個方法:
這裡的isUniquelyReferencedNonObjC方法會判斷你目前通路的MyWrapper的_object屬性是否隻有一個引用,如果是通過 let b = a 這樣的方法建立的話,那麼a的_object和b的_object的引用會指向同一個位址,在讀取值的時候不關心_object對象的引用,如果隻進行讀取的話是不需要進行copy的,如果需要寫入b的_objc話需要通路objectForWriting,此時objectForWriting會檢查_object的引用,如果如let b = a 的方式建立的b對象,那麼b中_object的引用是2,此時會将b的_object對象賦成_object拷貝後的副本,這樣在之後繼續通路objectForWriting的時候由于b中的_object已經是新的對象了,引用隻有1,是以_object會被直接傳回。Swift中的很多會包含引用對象的值對象都采用了Copy-on-Write技術,比如我們常用的數組。需要注意的是這裡的引用類型的對象必須是Swift對象,如果你使用了一個OC中的引用類型,那麼你需要對其進行一個封裝。
用法如下:
其中A是非Swift原生對象,在類中聲明某個對象的時候使用”Box”,在取值的時候需要調用Box執行個體的unbox屬性獲得原始的對象。除了使用“=”會發生copy外,向一個方法中傳入值類型時也會發生copy,是以可以安全地操作傳入的參數。通常情況下Swift中不會直接操作一個指針,所有的指針都會被标注為“unsafe”,如果想要方法改變傳入的參數,可以把該參數聲明成inout,然後在傳入時參數前加&,&代表傳入的是一個位址,inout的形式與直接操作某個對象的指針看起來是相同的,但是inout其實依舊使用了copy,不同的是在方法體結束的時候會把處理後的copy對象的值再賦回給原始的對象,請看下面的例子:
我們知道閉包可以捕獲對象,閉包中捕獲的是形參對象num,如果閉包num和a指向同一個位址的話,那麼在method方法體中clo捕獲的應該是a的引用,但其實clo捕獲的隻是copy後的副本,而已,在a發生變化之前就已經将副本捕獲了,導緻最後的列印結果不同。
下面來聊聊Struct,所有屬性的類型都确定的Struct會被儲存在棧上,Swift中每個Struct類型的長度是固定的,好比每個指針都是8位元組,是以指針會被儲存在棧上。在使用引用類型的時候,每次從棧上取到對應的指針需要根據指針提供的位址資訊去堆上尋找引用類型的真實值,定長的Struct的值會被直接儲存在棧上,這就省去了尋址的開銷,棧的空間是有限的,如果一組值類型的長度超過了棧的空間,那麼會被自動轉移到堆上。
讓我們在playground上寫一些示例:
這裡Int的長度是8位元組,String的長度是24位元組,最終StructDemo的長度是32,完全取決于其屬性的長度,sizeof和sizeofValue分别列印類型和執行個體的長度,這兩個方法顯示的都是棧上的長度,結果是相同的:
如果換成用class聲明,長度是8。