1绍豁、類(lèi)和結(jié)構(gòu)體
1.1 基礎(chǔ)認(rèn)知
類(lèi)和結(jié)構(gòu)體十分相似忌傻,如:
class/struct Person{
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
相同點(diǎn):
- 定義存儲(chǔ)值的屬性、方法焊夸、初始化器、以及下標(biāo)以使用下標(biāo)語(yǔ)法提供對(duì)其的訪(fǎng)問(wèn)
- 使用extension來(lái)擴(kuò)展功能
- 遵循協(xié)議來(lái)提供功能
不同點(diǎn):
- 類(lèi)能繼承蓝角,結(jié)構(gòu)體不行
- 類(lèi)型轉(zhuǎn)換使你能夠在運(yùn)行時(shí)檢查和解釋類(lèi)實(shí)例的類(lèi)型
- 類(lèi)有析構(gòu)函數(shù)用來(lái)釋放其分配的資源
- 引用計(jì)數(shù)允許對(duì)一個(gè)類(lèi)實(shí)例有多個(gè)引用
1.2 引用類(lèi)型和值類(lèi)型
引用類(lèi)型:即一個(gè)類(lèi)類(lèi)型的變量并不直接存儲(chǔ)具體的實(shí)例對(duì)象阱穗,是對(duì)當(dāng)前存儲(chǔ)具體實(shí)例內(nèi)存地址的引用
值類(lèi)型:相比較類(lèi)類(lèi)型的變量中存儲(chǔ)的是地址,那么值類(lèi)型存儲(chǔ)的就是具體的實(shí)例
- 要區(qū)分的第一件事就是:類(lèi)是引用類(lèi)型帅容、結(jié)構(gòu)體是值類(lèi)型颇象,可以借助lldb的po和x/8g指令來(lái)查看當(dāng)前的變量的內(nèi)存結(jié)構(gòu)
po:p和po的區(qū)別在于使用po只會(huì)輸出對(duì)應(yīng)的值,而p則會(huì)返回值的類(lèi)型以及命令結(jié)果的引用名并徘。
x/8g:讀取內(nèi)存中的值(8g:8字節(jié)格式的輸出)
我們可以通過(guò)運(yùn)行以下代碼來(lái)查看下內(nèi)存結(jié)構(gòu):
class/struct Person{
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
var person = Person(age: 10, name: "小明")
var person_1 = person
結(jié)果1:person和person_1存放了相同的內(nèi)存地址
結(jié)果2:person和person_1存放了相同的結(jié)構(gòu)體實(shí)例
- 要區(qū)分的第二件事:引用類(lèi)型和值類(lèi)型的存儲(chǔ)位置不同:一般情況下遣钳,值類(lèi)型存儲(chǔ)在棧上,引用類(lèi)型存儲(chǔ)在堆上
內(nèi)存區(qū)域的一些基本認(rèn)知
1麦乞、內(nèi)存的排列是從高到低
2蕴茴、一般只有從0x00000001到0x000007F之間的內(nèi)存才是我們可以操作的區(qū)域
3、一般我們把操作區(qū)域分為Stack(棧 - 存放的是局部變量和函數(shù)運(yùn)行過(guò)程中的上下文)姐直,Heap(堆 - 存儲(chǔ)所有對(duì)象)倦淀,Global(全局區(qū) + 常量區(qū) + 指令區(qū) - 存儲(chǔ)全局變量 + 常量 + 代碼區(qū))
4、當(dāng)棧和堆拉伸后導(dǎo)致內(nèi)存地址重合声畏,即溢出
5撞叽、內(nèi)存分配椧龀桑空間比堆空間快,因?yàn)槎芽臻g需要查找合適的區(qū)域再拷貝值愿棋,完成實(shí)例對(duì)象分配科展,離開(kāi)作用域后銷(xiāo)毀查找并把內(nèi)存塊插入,而椏酚辏空間每次會(huì)直接分配新的空間才睹,離開(kāi)作用域直接回滾銷(xiāo)毀。簡(jiǎn)單來(lái)說(shuō)就是堆始終需要尋找甘邀,而棧不用
所以鑒于堆在時(shí)間和效率上略差琅攘,應(yīng)將class盡量用struct替換,也就是盡量用值類(lèi)型替換引用類(lèi)型
1.3 class和struct的內(nèi)存分布
已知class是在堆松邪,struct在棧坞琴,那么具體內(nèi)存分布又會(huì)是如何,借助frame variable命令進(jìn)行查看
frame varibale -L xxx 查看變量?jī)?nèi)存地址
-
class:
通過(guò)打印結(jié)果可以知道類(lèi)的內(nèi)存分布是比較復(fù)雜的逗抑,首先8字節(jié)存的是指向類(lèi)型信息置济,第二個(gè)8字節(jié)是引用計(jì)數(shù),然后才是屬性信息锋八,p變量仍在堆上,只是存的棧地址
內(nèi)存存值 | 說(shuō)明 |
---|---|
0x0000000100008230 | 指向類(lèi)型信息(Metadata) |
3 | 引用計(jì)數(shù)(RefCount) |
10 | person.age |
小明 | person.name |
0x0000000100617338 | person.p |
-
struct:
通過(guò)打印結(jié)果可知結(jié)構(gòu)體的內(nèi)存分布是比較簡(jiǎn)單的护盈,是直接分布在棧上挟纱,即使結(jié)構(gòu)體內(nèi)部含有引用類(lèi)型,p變量仍然在棧上腐宋,只是存的是person這個(gè)對(duì)象的堆空間地址紊服,也就是說(shuō)
內(nèi)存存值 | 說(shuō)明 |
---|---|
10 | person.age |
小明 | person.name |
0x0000000109c04110 | person.p |
2、類(lèi)的初始化器
class Person {
var name: String
var age: Int
// 指定初始化器 (Designated Initializer) + 可失敗 胸竞?
init?(name: String, age: Int, minAge: Int) {
if minAge > age {
return nil
}
self.name = name
self.age = age
}
// 指定初始化器 (Designated Initializer) + 必需
required init(name: String, age: Int) {
self.name = name
self.age = age
}
// 便捷初始化器 (Convenience Initializer) + 必需 + 可失敗
convenience required init?(name: String) {
let defaultAge: Int = 8
self.init(name: name, age: defaultAge)
}
// 便捷初始化器 (Convenience Initializer)
convenience init(age: Int) {
let defaultName: String = "小明"
self.init(name: defaultName, age: age)
}
}
class People : Person{
var address : String
override init?(name: String, age: Int, minAge: Int) {
//指定初始化器必須保證在向上委托給父類(lèi)初始化器之前欺嗤,其所在類(lèi)引入的所有屬性都要初始化完成
self.address = "北京"
super.init(name: name, age: age, minAge: minAge)
//指定初始化器必須先向上委托父類(lèi)初始化器,然后才能為繼承的屬性設(shè)置新值
self.name = "小紅"
}
convenience required init?(name: String) {
//required 必需繼承卫枝,便捷初始化器必須先委托同類(lèi)中的其它初始化器
self.init(name: name, age: 8, minAge: 1)
}
required init(name: String, age: Int) {
self.address = "北京"
super.init(name: name, age: age)
}
}
- 當(dāng)前的類(lèi)編譯器默認(rèn)不會(huì)自動(dòng)提供成員初始化器煎饼,但是對(duì)于結(jié)構(gòu)體來(lái)說(shuō)編譯 器會(huì)提供默認(rèn)的初始化方法(前提是我們自己沒(méi)有指定初始化器)!
- 創(chuàng)建類(lèi)和結(jié)構(gòu)體的實(shí)例時(shí)必須為所有的存儲(chǔ)屬性設(shè)置一個(gè)合適的初始值校赤!
1吆玖、指定初始化器必須保證在向上委托給父類(lèi)初始化器之前,其所在類(lèi)引入的所有屬性都要初始化完成马篮。
2沾乘、指定初始化器必須先向上委托父類(lèi)初始化器,然后才能為繼承的屬性設(shè)置新值浑测。如果不這樣做翅阵,指定初始化器賦予的新值將被父類(lèi)中的初始化器所覆蓋
3、便捷初始化器必須先委托同類(lèi)中的其它初始化器,然后再為任意屬性賦新值(包括同類(lèi)里定義的屬性)掷匠。如果沒(méi)這么做滥崩,便捷構(gòu)初始化器賦予的新值將被自己類(lèi)中其它指定初始化器所覆蓋。
4槐雾、初始化器在第一階段初始化完成之前夭委,不能調(diào)用任何實(shí)例方法、不能讀取任何實(shí)例屬性的值募强,也不能引用 self 作為值株灸。
- 可失敗初始化器:就意味著當(dāng)前因?yàn)閰?shù)的不合法或者外部條件的不滿(mǎn)足,存在初始化失敗的情況擎值。這種 Swift 中可失敗初始化器寫(xiě) return nil 語(yǔ)句(init?())
- 必要初始化器:在類(lèi)的初始化器前添加 required 修飾符來(lái)表明所有該類(lèi)的子類(lèi)都必須 實(shí)現(xiàn)該初始化器(requeired init())
3慌烧、類(lèi)的生命周期和數(shù)據(jù)結(jié)構(gòu)
- OC和Swift均是通過(guò)LLVM進(jìn)行編譯的
OC 通過(guò) clang 編譯器,編譯成 IR鸠儿,然后再生成可執(zhí)行文件 .o(這里也就是我們的機(jī)器碼)
Swift 則是通過(guò) Swift 編譯器編譯成 IR屹蚊,然后在生成可執(zhí)行文件。
- Swift 對(duì)象內(nèi)存分配:
__allocating_init -> swift_allocObject -> swift_allocObject -> swift_slowAlloc -> Malloc - Swift 對(duì)象的內(nèi)存結(jié)構(gòu) HeapObject (OC objc_object) 进每,有兩個(gè)屬性: 一個(gè)是Metadata 汹粤,一個(gè)是 RefCount ,默認(rèn)占用 16 字節(jié)大小田晚。
- Swift類(lèi)的數(shù)據(jù)結(jié)構(gòu)
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}