在 swift 中,類和結(jié)構(gòu)體都可以定義方法.那么類和結(jié)構(gòu)體定義的方法在調(diào)用的時候有什么不同嗎?我們看看下面的代碼.
首先看看調(diào)用結(jié)構(gòu)體方法的匯編:
可以看到調(diào)用結(jié)構(gòu)體方法時,方法的地址是固定的.
我們把struct
改成class
,再來看看他的的匯編:
可以看到,方法地址不是固定的,而是通過一定計算得來的.因為class
支持繼承,所以它的方法地址一定不是寫死的,而是通過計算得來的.
所以在項目中如果我們只是單純的調(diào)用方法,那我們用結(jié)構(gòu)體就可以了,從匯編代碼可以看出,結(jié)構(gòu)體的匯編代碼更少,流程更簡單,效率更快.
一: swift 多態(tài)的實現(xiàn)原理
下面我們就來研究 Swift 中,多態(tài)的實現(xiàn)原理.
示例代碼:
class Person{
func eat(){
print("Person eat")
}
func run(){
print("Person run")
}
func sleep(){
print("Person sleep")
}
}
class Student: Person {
override func eat() {
print("Student eat")
}
override func run() {
print("Student run")
}
func study(){
print("Student stuty")
}
}
var person = Person()
person = Student()
person.eat()
person.run()
person.sleep()
//運行結(jié)果:
Student eat
Student run
Person sleep
上面代碼,父類類型Person
指向子類Student
的實例對象,最后調(diào)用的的結(jié)果.這就是多態(tài),swfit 內(nèi)部是怎樣實現(xiàn)多態(tài)的呢?
我們還是通過匯編窺探一下:
我們知道person
是一個指針變量,它里面存儲的是堆空間對象的地址值.從匯編代碼可以看到,調(diào)用eat()
時,先把person
中存儲的堆空間對象的地址值取出來,然后movq
指令取出堆空間的前8個字節(jié),也就是類型信息的地址.然后通過一個偏移量找到eat()
方法的地址,并調(diào)用的.
畫圖表示:
上圖就是子類在調(diào)用方法的時候是如何調(diào)用的流程,需要注意的是類型信息存儲在全局區(qū)
二: 初始化器
之前講過,初始化器的唯一原則就是:保證所有成員都有值.在 swift 中,初始化器可以分為兩種:1: 指定初始化器
, 2:便捷初始化器
1:指定初始化器
指定初始化器就是之前我們使用的初始化器,如:
或者我們自定義的初始化器:
以上兩種都是指定初始化器,需要注意的是,一旦我們自定義了初始化器,編譯器就不會為我們再生成初始化器了.
指定初始化器就是類的主要初始化器,可以理解為主線.一個類至少有一個指定初始化器,并且 swift 官方建議不要過多的指定初始化器,一個類通常只有一個指定初始化器.
1: 便捷初始化器
便捷初始化器,顧名思義就是為了便捷,為了方便,比如:
可以看到,我們可以根據(jù)自己的需求定義多個便捷初始化器.
注意: 在便捷初始化器內(nèi)部必須調(diào)用指定初始化器,不然會報錯
如果有繼承關(guān)系的類,子類的指定初始化器必須調(diào)用
直系父類的指定初始化器
總結(jié):
1. 如果是指定初始化器,必須調(diào)用直系父類的指定初始化器;
2. 如果是便捷初始化器,必須調(diào)用自己的指定初始化器;
那便捷初始化器可不可以調(diào)用便捷初始化器呢?
從上圖可以看到,便捷初始化器是可以調(diào)用便捷初始化器的,但是必須遵從一條規(guī)則:便捷初始化器最終必須要調(diào)用指定初始化器
三: 初始化過程
swift 的初始化工作有著一套完善且安全的流程,如果不搞清楚初始化流程,很容易出現(xiàn)各種各樣的錯誤,比如:
swift 的初始化過程可以分為兩個階段1:初始化所有屬性
,2:正常使用 self 對象
一: 初始化所有屬性
上面已經(jīng)講過,初始化器唯一的原則就是要保證所有屬性都被初始化.我們把從外部調(diào)用初始化器開始到所有屬性初始化完畢這一階段的流程梳理一下:
外部調(diào)用初始化器
(便捷初始化器或者指定初始化器)
分配堆空間內(nèi)存,此時還未初始化
-
指定初始化器必須先確保當(dāng)前類所有的存儲屬性都被初始化
3.1 如果當(dāng)前類的屬性沒有初始化就調(diào)用父類初始化器,會直接報錯:
調(diào)用父類指定初始化器,一直向上調(diào)用,直到基類,形成初始化鏈.
二: 正常使用 self 對象
第一階段完成后,所有子類和基類的所有屬性都已經(jīng)初始化了.此時會從基類開始,從上到下依次初始化完成.此時才可以正常使用self
如果第一階段沒有完成,是無法正常使用self
的:
也就是說,
self的必須在
init之后使用
四: 初始化器的重寫
- 當(dāng)子類重寫父類的指定初始化器時,必須加上
override
關(guān)鍵字.子類可以把父類的指定初始化器重寫為指定初始化器和便捷初始化器.
class Person{
var age: Int
var name: String
init(){
self.age = 0
self.name = ""
}
init(age: Int, name: String){
self.age = age
self.name = name
}
}
class Student: Person {
var score: Int
//重寫父類無參的指定初始化器
override init() {
self.score = 0
super.init()
}
//把父類指定初始化器重寫為 便捷初始化器
override convenience init(age: Int, name: String) {
self.init()
}
}
- 如果子類寫了一個和父類便捷初始化器一模一樣的初始化器,不用加
override
,嚴(yán)格來講,這并不是重寫.
因為便捷初始化器是橫向調(diào)用的,它只能在類本身調(diào)用,子類是不能調(diào)用父類的便捷初始化器的.而重寫可以通過super
調(diào)用父類的方法,但是卻不能通過super
調(diào)用父類的便捷初始化器,這就沖突了.
四: 初始化器的繼承
- 如果子類沒有自定義任何指定初始化器,它將繼承父類所有指定初始化器
注意我們我們說的是沒有定義任何指定初始化器,就可以繼承父類的所有指定初始化器.所以即使我們定義了便捷初始化器,仍然會繼承父類所有的指定初始化器:
- 如果子類提供了父類所有指定初始化器的實現(xiàn)(
兩種方式: 1: 通過第一條繼承 2: 通過重寫
),子類會自動繼承所有父類的便捷初始化器
五: required
用required
修飾的指定初始化器,注意是指定初始化器
.表明所有的子類都必須實現(xiàn)此指定初始化器.并且子類實現(xiàn)的時候不需加override
,只需要加上required
即可.如果我們要強制子類必須實現(xiàn)某一個初始化器,可以使用required
關(guān)鍵字.
六: 可失敗初始化器
枚舉,結(jié)構(gòu)體,類都可以用init? 或者 init!
定義可失敗初始化器.
可失敗初始化器要注意以下幾點:
- 可失敗初始化器可以重寫為非可失敗初始化器;但是非可失敗初始化器不能重寫為可失敗初始化器.這也很容易理解:
可失敗(0 和 1)
包容非可失敗(1)
.
2.在非可失敗初始化器中調(diào)用可失敗初始化器,要加感嘆號!
,但是這樣做不安全