結構體
- 在 Swift 標準庫中,絕大多數的公開類型都是結構體,而枚舉和類只佔很小一部分
- 比如Bool、Int、Double、 String、Array、Dictionary等常見類型都是結構體
- 在第6行調用的,可以傳入所有成員值,用以初始化所有成員(存儲屬性,Stored Property)
結構體的初始化器
編譯器會根據情況,可能會爲結構體生成多個初始化器,宗旨是:保證所有成員都有初始值
上面的Point在聲明時沒有爲先x, y初始化值,p1給x和y賦值了,p2,p3,p4沒有爲x,y賦全值
x有初始化值,y沒有初始化值
y有初始化值,x沒有初始化值
x,y都有初始化值
- 下面的代碼能編譯通過嗎?
因爲可選項都有個默認值nil,因此可以編譯通過
結構體的初始化器
- 一旦在定義結構體時自定義了初始化器,編譯器就不會自動再幫它自動生成其他初始化器
這時不會默認初始化值,需要傳入,否則報錯
- 以下兩段代碼完全等效
- 結構體的內存結構
類
- 類的定義和結構體類似,但編譯器並沒有爲類自動生成可以傳入成員值的初始化器
由上可得:類的屬性一定要有初始化值
- 如果類的所有成員都在定義的時候指定了初始值,編譯器會爲類生成無參的初始化器,成員的初始化是在這個初始化器中完成的
- 上面2段代碼是完全等效的
結構體與類的本質區別
結構體是值類型(枚舉也是值類型),類是引用類型(指針類型)
由上可以看出:point也就是結構體存儲在棧空間,size也就是類存儲在堆空間
上圖都是針對64bit環境
值類型
- 值類型賦值給var、let或者給函數傳參,是直接將所有內容拷貝一份, 類似於對文件進行copy、paste操作,產生了全新的文件副本。屬於深拷貝(deep copy)
由於是值類型,互不影響,所以p1.x = 10, p1.y = 20
值類型的賦值
- 在Swift標準庫中,爲了提升性能,String、Array、Dictionary、Set採取了Copy On Write的技術
- 比如僅當有“寫”操作時,纔會真正執行拷貝操作
- 對於標準庫值類型的賦值操作,Swift 能確保最佳性能,所有沒必要爲了保證最佳性能來避免賦值
- Swift標準庫中的結構體爲保持性能,只有在結構體被賦值並且有寫入的時候才進行深拷貝,但是自己定義的結構體,只要結構體有被賦值操作就會深拷貝。
- 建議:不需要修改的,儘量定義成let
引用類型
- 引用賦值給var、let或者給函數傳參,是將內存地址拷貝一份
- 類似於製作一個文件的替身(快捷方式、鏈接),指向的是同一個文件。屬於淺拷貝(shallow copy)
屬於淺拷貝,s1.width = 11, s2.height = 22
對象的堆空間的申請過程
- 在Swift中,創建類的實例對象,要向堆空間申請內存,大概流程如下
- Class.__allocating_init()
- libswiftCore.dylib:_swift_allocObject_
- libswiftCore.dylib:swift_slowAlloc
- libsystem_malloc.dylib:malloc
- 在Mac、iOS中的malloc函數分配的內存大小總是16的倍數
- 通過class_getInstanceSize可以得知類的對象真正使用的內存大小
具體解析內存佔用:
import Foundation
func testInstance() {
class Point {
//引用類型前面會有16個字節
var x : Int = 11 //8
var test = true //1
var y : Int = 22 //8
}
let p = Point()
print(class_getInstanceSize(Point.self)) //40
print(class_getInstanceSize(type(of: p))) //40
print(Mems.size(ofRef:p)) //48
//33 類對象真正利用,真正存具體數據的內存空間
//40 類的對象真正使用的內存大小
//48 類對象實際分配的內存大小
}
引用類型的賦值操作
值類型、引用類型的let
p表示值類型賦值的內存數據是常量,不可變,也就是10和20不可變,下面對p的三個賦值操作都改變了棧空間的內存數據,所以報錯
s表示引用類型賦值的內存數據是常量,這裏的棧空間存儲的內存數據是指向堆內存空間的地址,第一個屬於重新賦一個新值,指向的內存地址改變,所以報錯,但是後兩個沒有改變指向的堆內存的地址,所以沒有報錯
嵌套類型
枚舉、類、結構體都可以定義方法
一般把定義在枚舉、結構體、類內部的函數,叫做方法
枚舉:
類:
結構體:
注意;方法不佔用實力對象內存的,方法的本質就是函數,方法實際上是放在代碼段的.