一. 多態(tài)實現(xiàn)原理
多態(tài)就是父類指針指向子類對象荠察。
關(guān)于多態(tài):在編譯的時候并不知道要調(diào)用的是父類還是子類的方法置蜀,運(yùn)行的時候才會根據(jù)實際類型調(diào)用子類的方法。
對于結(jié)構(gòu)體來說悉盆,因為結(jié)構(gòu)體沒有繼承盯荤,編譯的時候就能知道調(diào)用哪個方法。
但是對于類焕盟,只有在運(yùn)行的時候才能知道實際調(diào)用哪個方法秋秤。
多態(tài)實現(xiàn)原理:
OC:通過Runtime將一些方法放到方法列表里面
C++:虛表(虛函數(shù)表)
Swift中的多態(tài):和虛表很像
class Animal {
func speak() {
print("Animal speak")
}
func eat() {
print("Animal eat")
}
func sleep() {
print("Animal sleep")
}
}
class Dog : Animal {
override func speak() {
print("Dog speak")
}
override func eat() {
print("Dog eat")
}
func run() {
print("Dog run")
}
}
var anim = Animal() //父類指針
anim = Dog() //指向子類對象
//這時候,編譯器提示脚翘,anim是Animal類型的航缀,但是實際是Dog類型的
anim.speak()
anim.eat()
anim.sleep()
打印:
Dog speak
Dog eat
Animal sleep
我們都知道是上面的打印結(jié)果堰怨,但是原理是什么呢芥玉?如下圖,下面的結(jié)論都是MJ老師通過窺探匯編得到的:
我們都知道Dog對象的前8個字節(jié)是指向類型相關(guān)备图,如上圖灿巧,類型相關(guān)的那塊區(qū)域里面,前面一塊區(qū)域存放Dog的類型信息相關(guān)揽涮,后面一塊區(qū)域存放方法的地址抠藕,比如Dog重寫了speak和eat,所以這兩個方法在最前面蒋困,沒有重寫sleep所以再后面是Animal.sleep盾似,最后是Dog.run。
當(dāng)調(diào)用anim.speak()雪标,首先先取出Dog對象的前8個字節(jié)零院,根據(jù)這8個字節(jié)指針找到指針指向的內(nèi)存,找到內(nèi)存地址之后加上一個固定偏移量村刨,就會獲得Dog.speak方法的內(nèi)存地址告抄,找到方法之后調(diào)用。
同理嵌牺,Dog eat也是先取出前8字節(jié)打洼,找到這8字節(jié)的內(nèi)存地址龄糊,然后再加上一個固定偏移量,找到Dog.eat方法募疮,找到方法之后調(diào)用炫惩。
如果Dog沒有重寫Animal的方法,那么類型信息那塊區(qū)域存儲的方法就是阿浓,如下圖:
如果是兩個dog對象诡必,他們的類型信息肯定也是一樣的,如下圖:
那么Dog的類型信息會放在代碼段搔扁、數(shù)據(jù)段裳仆、堆嗦锐、棧中的哪一段?
我們可以猜測一下皂冰,因為類型信息要一直在內(nèi)存中鹊奖,首先排除棧苛聘,代碼段一般放編譯之后的代碼也排除,堆空間一般放alloc忠聚,malloc等動態(tài)分配的東西设哗,也排除,所以Dog的類型信息只能存放數(shù)據(jù)段两蟀。
MJ老師通過打斷點查看匯編网梢,比較地址值也得出結(jié)論,的確是在數(shù)據(jù)段赂毯。
總結(jié):程序在編譯的時候就將函數(shù)地址放到了類型信息那塊內(nèi)存區(qū)域了战虏,當(dāng)程序運(yùn)行的時候再到這塊內(nèi)存區(qū)域找函數(shù)地址,找到之后就調(diào)用党涕。
二. 初始化器
1. 初始化器
枚舉:枚舉可以使?rawValue來給枚舉賦值烦感,沒有自動生成的初始化器。
結(jié)構(gòu)體:所有的結(jié)構(gòu)體都有編譯器?動?成的初始化器(也許不??個)膛堤,?于初始化所有成員手趣,但是如果你?定義了初始化器,編譯器就不會幫你了肥荔。
類:編譯器沒有為類?動?成可以傳?成員值的初始化器(想讓我們??寫)绿渣,但是如果屬性都有默認(rèn)值,也會幫我們創(chuàng)建?個?參初始化器燕耿。
當(dāng)然怯晕,枚舉、結(jié)構(gòu)體缸棵、類都可以自定義初始化器舟茶。
類有兩種初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer)
// 指定初始化器
init(parameters) {
statements
}
// 便捷初始化器
convenience init(parameters) {
statements
}
每個類至少有一個指定初始化器,指定初始化器是類的主要初始化器
默認(rèn)初始化器總是類的指定初始化器
類偏向于少量指定初始化器吧凉,一個類通常只有一個指定初始化器
初始化器的相互調(diào)用規(guī)則:
1. 指定初始化器必須從它的直系父類調(diào)用指定初始化器
2. 便捷初始化器必須從相同的類里調(diào)用另一個初始化器隧出,最終必須調(diào)用一個指定初始化器
(其實就是,便捷初始化器是橫向調(diào)用阀捅,指定初始化器是豎向調(diào)用)
這一套規(guī)則保證了使用任意初始化器,都可以完整地初始化實例饲鄙。
2. 兩段式初始化凄诞、安全檢查
Swift在編碼安全方面是煞費(fèi)苦心,為了保證初始化過程的安全忍级,設(shè)定了兩段式初始化帆谍、 安全檢查
① 兩段式初始化
第1階段:初始化所有存儲屬性
① 外層調(diào)用指定\便捷初始化器
② 分配內(nèi)存給實例,但未初始化
③ 指定初始化器確保當(dāng)前類定義的存儲屬性都初始化
④ 指定初始化器調(diào)用父類的初始化器轴咱,不斷向上調(diào)用汛蝙,形成初始化器鏈
第2階段:設(shè)置新的存儲屬性值
⑤ 從頂部初始化器往下,鏈中的每一個指定初始化器都有機(jī)會進(jìn)一步定制實例
⑥ 初始化器現(xiàn)在能夠使用self訪問朴肺、修改它的屬性窖剑,調(diào)用它的實例方法等等
⑦ 最終,鏈中任何便捷初始化器都有機(jī)會定制實例以及使用self
② 安全檢查
- 指定初始化器必須保證其所在類定義的所有存儲屬性都要初始化完成戈稿,再調(diào)用父類初始化器
- 指定初始化器必須先調(diào)用父類初始化器西土,然后才能為繼承的屬性設(shè)置新值
- 便捷初始化器必須先調(diào)用同類中的其它初始化器,然后再為任意屬性設(shè)置新值
- 初始化器在第1階段初始化完成之前鞍盗,不能調(diào)用任何實例方法翠储、不能讀取任何實例屬性的值,也不能引用self
- 直到第1階段結(jié)束橡疼,實例才算完全合法
光看上面兩段文字可能比較難理解援所,結(jié)合下面代碼,從第1步到第12步欣除,看完你就明白了:
class BasePerson {
var IDnum: Int
init(IDnum: Int) {
//這里打幼∈谩:print(self.IDnum)和方法調(diào)用self.test(),都會報錯:Variable 'self.IDnum' used before being initialized
self.IDnum = IDnum //8.父類的指定初始化器初始化自己的屬性
//9.至此第一階段結(jié)束历帚,才可以使用self
print(self.IDnum) //這里打印不會報錯
self.test() //方法調(diào)用也不會報錯
}
convenience init() {
self.init(IDnum: 1234567890)
}
func test() -> Void {
print("測試")
}
}
class Person : BasePerson {
var age: Int
var name: String
init(age: Int, name: String) { //5.來到父類的指定初始化器
self.age = age
self.name = name //6.父類也是先初始化自己的屬性
super.init(IDnum: 0) //7.再調(diào)用父類的指定初始化器
self.IDnum = 3412211992 //10.父類的初始化器調(diào)用完滔岳,接著進(jìn)一步定制實例
}
convenience init() {
self.init(age: 0, name: "")
}
convenience init(age: Int) {
self.init(age: age, name: "")
}
convenience init(name: String) {
self.init(age: 0, name: name)
}
}
class Student : Person {
var score: Int
init(age: Int, score:Int) {
self.score = score; //3.指定初始化器確保當(dāng)前類定義的存儲屬性都初始化
super.init(age: age, name: "") //4.指定初始化器調(diào)用父類的初始化器,不斷向上調(diào)用挽牢,形成初始化器鏈
self.age = 10 //11.父類的初始化器調(diào)用完谱煤,接著進(jìn)一步定制實例
}
convenience init() {
self.init(age: 0, score:0) //2.便捷初始化器調(diào)用指定初始化器
//12.至此第二階段結(jié)束,所有初始化完成
}
}
var stu = Student() //1.外層調(diào)用便捷初始化器
補(bǔ)充:一般我們自定義UIView都會重寫它的init(frame: CGRect)方法禽拔,如下:
override init(frame: CGRect) {
super.init(frame: frame)
setupLayout()
}
這時候你可能會疑惑為什么這里的super.init在最上面刘离?
其實不管是重寫初始化器還是自定義初始化器室叉,都是定義一個初始化器,重寫初始化器一般沒有新的參數(shù)(作為屬性)硫惕,所以super.init在最上面茧痕,如果是自定義一個新的初始化器,一般都有新的參數(shù)(作為屬性)恼除,所以就是上面的兩段式初始化踪旷。
3. 重寫、自動繼承豁辉、required令野、屬性觀察器
① 重寫
- 當(dāng)重寫父類的指定初始化器時,必須加上override(即使子類的實現(xiàn)是便捷初始化器)徽级。
代碼如下:
class Person {
var age: Int
init(age: Int) {
self.age = age
}
convenience init() {
self.init(age:0)
}
}
class Student: Person {
var score: Int
init(age: Int, score: Int) {
self.score = score
super.init(age: age)
}
// 子類重寫父類的指定初始化器為指定初始化器
// override init(age: Int) {
// self.score = 0
// super.init(age: age)
// }
// 子類重寫父類的指定初始化器為便捷初始化器
override convenience init(age: Int) {
self.init(age: age, score: 0)
}
}
- 如果子類寫了一個匹配父類便捷初始化器的初始化器气破,不用加上override。因為父類的便捷初始化器永遠(yuǎn)不會通過子類直接調(diào)用灰追,因此,嚴(yán)格來說狗超,子類無法重寫父類的便捷初始化器弹澎。
代碼如下:
class Person {
var age: Int
init(age: Int) {
self.age = age
}
convenience init() {
self.init(age:0)
}
}
class Student: Person {
var score: Int
init(age: Int, score: Int) {
self.score = score
super.init(age: age)
}
//也不報錯
init() {
self.score = 0
super.init(age: 0)
}
//不報錯
// convenience init() {
// self.init(age: 0, score: 0)
// }
//上面不叫重寫,我們假設(shè)它是重寫父類的便捷初始化器init()努咐,既然是重寫苦蒿,那么在子類的方法中就可以調(diào)用super.init()
//很顯然,對于指定初始化器只能調(diào)用父類的指定初始化器渗稍,不可以調(diào)用super.init()佩迟。
//對于便捷初始化器只能調(diào)用子類的指定初始化器,更不可能調(diào)用super.init()竿屹,所以上面不叫重寫报强。
}
② 自動繼承
- 如果子類沒有自定義任何指定初始化器,它會自動繼承父類所有的指定初始化器(一旦你自定義了一個指定初始化器拱燃,那么父類的指定初始化器統(tǒng)統(tǒng)不給你用了)
- 如果子類提供了父類所有指定初始化器的實現(xiàn)(要么通過方式1繼承秉溉,要么重寫),那么子類自動繼承所有的父類便捷初始化器
- 子類以便捷初始化器的形式重寫父類的指定初始化器碗誉,也可以作為滿足規(guī)則2的一部分
- 就算子類添加了更多的便捷初始化器召嘶,這些規(guī)則仍然適用
補(bǔ)充:我們一般不自定義指定初始化器,這樣才能自動繼承父類的指定初始化器哮缺,我們一般自定義便捷初始化器弄跌,如下:
//便捷初始化器必須先調(diào)用同類中的其它初始化器(這里是繼承父類的),然后再為任意屬性設(shè)置新值
convenience init(titles: [String] = [], vcs: [UIViewController] = [], pageStyle: XUPageStyle = .noneStyle) {
self.init()
self.titles = titles
self.vcs = vcs
self.pageStyle = pageStyle
}
③ required
- 用required修飾指定初始化器(修飾便捷初始化器沒啥用)尝苇,表明其所有子類都必須實現(xiàn)該初始化器(通過繼承或者重寫實現(xiàn))
- 如果子類重寫了required初始化器铛只,也必須加上required埠胖,不用加override
class Person {
required init() { }
init(age: Int) { }
}
class Student : Person {
required init() {
super.init()
}
}
④ 屬性觀察器
父類的屬性在它自己的初始化器中賦值不會觸發(fā)屬性觀察器,但在子類的初始化器中賦值會觸發(fā)屬性觀察器
class Person {
var age: Int {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, age)
}
}
init() {
self.age = 0
}
}
class Student : Person {
override init() {
super.init()
self.age = 1
}
}
// willSet 1
// didSet 0 1
var stu = Student()
這個也很容易理解格仲,age本來就是從父類來的押袍,你修改了父類的屬性,肯定要觸發(fā)父類的屬性觀察器凯肋。
4. 可失敗初始化器谊惭、反初始化器
① 可失敗初始化器
枚舉、結(jié)構(gòu)體侮东、類都可以使用init?定義可失敗初始化器圈盔。
如下:
class Person {
var name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
之前接觸過的可失敗初始化器,如下:
var num = Int("123") //源碼就是下一行
public init?(_ description: String)
enum Answer : Int {
case wrong, right
}
var an = Answer(rawValue: 1) //如果傳入2就創(chuàng)建失敗悄雅,所以是可失敗的
- 不允許同時定義參數(shù)標(biāo)簽驱敲、參數(shù)個數(shù)、參數(shù)類型相同的可失敗初始化器和非可失敗初始化器(因為不知道調(diào)用哪個了)
- 可以用init!定義隱式解包的可失敗初始化器
- 可失敗初始化器可以調(diào)用非可失敗初始化器宽闲,非可失敗初始化器調(diào)用可失敗初始化器需要進(jìn)行解包
- 如果初始化器調(diào)用一個可失敗初始化器導(dǎo)致初始化失敗众眨,那么整個初始化過程都失敗,并且之后的代碼都停止執(zhí)行
- 可以用一個非可失敗初始化器重寫一個可失敗初始化器容诬,但反過來是不行的娩梨。
② 反初始化器(deinit)
deinit叫做反初始化器,類似于C++的析構(gòu)函數(shù)览徒、OC中的dealloc方法
當(dāng)類的實例對象被釋放內(nèi)存時狈定,就會調(diào)用實例對象的deinit方法
class Person {
deinit {
print("Person deinit")
}
}
- deinit不接受任何參數(shù),不能寫小括號
- 父類的deinit能被子類繼承
- 子類的deinit實現(xiàn)執(zhí)行完畢后會調(diào)用父類的deinit
如下习蓬,子類沒有實現(xiàn)deinit纽什,通過打印可知,子類繼承了父類的deinit
class Person {
deinit {
print("Person deinit")
}
}
class Student: Person {
}
func test() {
var stu = Student()
}
test() //打佣愕稹:Person deinit