一. 多态实现原理
多态就是父类指针指向子类对象。
关于多态:在编译的时候并不知道要调用的是父类还是子类的方法,运行的时候才会根据实际类型调用子类的方法。
对于结构体来说,因为结构体没有继承,编译的时候就能知道调用哪个方法。
但是对于类,只有在运行的时候才能知道实际调用哪个方法。
多态实现原理:
OC:通过Runtime将一些方法放到方法列表里面
C++:虚表(虚函数表)
Swift中的多态:和虚表很像
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
我们都知道是上面的打印结果,但是原理是什么呢?如下图,下面的结论都是MJ老师通过窥探汇编得到的:
我们都知道Dog对象的前8个字节是指向类型相关,如上图,类型相关的那块区域里面,前面一块区域存放Dog的类型信息相关,后面一块区域存放方法的地址,比如Dog重写了speak和eat,所以这两个方法在最前面,没有重写sleep所以再后面是Animal.sleep,最后是Dog.run。
当调用anim.speak(),首先先取出Dog对象的前8个字节,根据这8个字节指针找到指针指向的内存,找到内存地址之后加上一个固定偏移量,就会获得Dog.speak方法的内存地址,找到方法之后调用。
同理,Dog eat也是先取出前8字节,找到这8字节的内存地址,然后再加上一个固定偏移量,找到Dog.eat方法,找到方法之后调用。
如果Dog没有重写Animal的方法,那么类型信息那块区域存储的方法就是,如下图:
如果是两个dog对象,他们的类型信息肯定也是一样的,如下图:
那么Dog的类型信息会放在代码段、数据段、堆、栈中的哪一段?
我们可以猜测一下,因为类型信息要一直在内存中,首先排除栈,代码段一般放编译之后的代码也排除,堆空间一般放alloc,malloc等动态分配的东西,也排除,所以Dog的类型信息只能存放数据段。
MJ老师通过打断点查看汇编,比较地址值也得出结论,的确是在数据段。
总结:程序在编译的时候就将函数地址放到了类型信息那块内存区域了,当程序运行的时候再到这块内存区域找函数地址,找到之后就调用。
二. 初始化器
1. 初始化器
枚举:枚举可以使⽤rawValue来给枚举赋值,没有自动生成的初始化器。
结构体:所有的结构体都有编译器⾃动⽣成的初始化器(也许不⽌⼀个),⽤于初始化所有成员,但是如果你⾃定义了初始化器,编译器就不会帮你了。
类:编译器没有为类⾃动⽣成可以传⼊成员值的初始化器(想让我们⾃⼰写),但是如果属性都有默认值,也会帮我们创建⼀个⽆参初始化器。
当然,枚举、结构体、类都可以自定义初始化器。
类有两种初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer)
// 指定初始化器
init(parameters) {
statements
}
// 便捷初始化器
convenience init(parameters) {
statements
}
每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器
默认初始化器总是类的指定初始化器
类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
初始化器的相互调用规则:
1. 指定初始化器必须从它的直系父类调用指定初始化器
2. 便捷初始化器必须从相同的类里调用另一个初始化器,最终必须调用一个指定初始化器
(其实就是,便捷初始化器是横向调用,指定初始化器是竖向调用)
这一套规则保证了使用任意初始化器,都可以完整地初始化实例。
2. 两段式初始化、安全检查
Swift在编码安全方面是煞费苦心,为了保证初始化过程的安全,设定了两段式初始化、 安全检查
① 两段式初始化
第1阶段:初始化所有存储属性
① 外层调用指定\便捷初始化器
② 分配内存给实例,但未初始化
③ 指定初始化器确保当前类定义的存储属性都初始化
④ 指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链
第2阶段:设置新的存储属性值
⑤ 从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例
⑥ 初始化器现在能够使用self访问、修改它的属性,调用它的实例方法等等
⑦ 最终,链中任何便捷初始化器都有机会定制实例以及使用self
② 安全检查
- 指定初始化器必须保证其所在类定义的所有存储属性都要初始化完成,再调用父类初始化器
- 指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值
- 便捷初始化器必须先调用同类中的其它初始化器,然后再为任意属性设置新值
- 初始化器在第1阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self
- 直到第1阶段结束,实例才算完全合法
光看上面两段文字可能比较难理解,结合下面代码,从第1步到第12步,看完你就明白了:
class BasePerson {
var IDnum: Int
init(IDnum: Int) {
//这里打印:print(self.IDnum)和方法调用self.test(),都会报错:Variable 'self.IDnum' used before being initialized
self.IDnum = IDnum //8.父类的指定初始化器初始化自己的属性
//9.至此第一阶段结束,才可以使用self
print(self.IDnum) //这里打印不会报错
self.test() //方法调用也不会报错
}
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.再调用父类的指定初始化器
self.IDnum = 3412211992 //10.父类的初始化器调用完,接着进一步定制实例
}
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.指定初始化器确保当前类定义的存储属性都初始化
super.init(age: age, name: "") //4.指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链
self.age = 10 //11.父类的初始化器调用完,接着进一步定制实例
}
convenience init() {
self.init(age: 0, score:0) //2.便捷初始化器调用指定初始化器
//12.至此第二阶段结束,所有初始化完成
}
}
var stu = Student() //1.外层调用便捷初始化器
补充:一般我们自定义UIView都会重写它的init(frame: CGRect)方法,如下:
override init(frame: CGRect) {
super.init(frame: frame)
setupLayout()
}
这时候你可能会疑惑为什么这里的super.init在最上面?
其实不管是重写初始化器还是自定义初始化器,都是定义一个初始化器,重写初始化器一般没有新的参数(作为属性),所以super.init在最上面,如果是自定义一个新的初始化器,一般都有新的参数(作为属性),所以就是上面的两段式初始化。
3. 重写、自动继承、required、属性观察器
① 重写
- 当重写父类的指定初始化器时,必须加上override(即使子类的实现是便捷初始化器)。
代码如下:
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。因为父类的便捷初始化器永远不会通过子类直接调用,因此,严格来说,子类无法重写父类的便捷初始化器。
代码如下:
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)
// }
//上面不叫重写,我们假设它是重写父类的便捷初始化器init(),既然是重写,那么在子类的方法中就可以调用super.init()
//很显然,对于指定初始化器只能调用父类的指定初始化器,不可以调用super.init()。
//对于便捷初始化器只能调用子类的指定初始化器,更不可能调用super.init(),所以上面不叫重写。
}
② 自动继承
- 如果子类没有自定义任何指定初始化器,它会自动继承父类所有的指定初始化器(一旦你自定义了一个指定初始化器,那么父类的指定初始化器统统不给你用了)
- 如果子类提供了父类所有指定初始化器的实现(要么通过方式1继承,要么重写),那么子类自动继承所有的父类便捷初始化器
- 子类以便捷初始化器的形式重写父类的指定初始化器,也可以作为满足规则2的一部分
- 就算子类添加了更多的便捷初始化器,这些规则仍然适用
补充:我们一般不自定义指定初始化器,这样才能自动继承父类的指定初始化器,我们一般自定义便捷初始化器,如下:
//便捷初始化器必须先调用同类中的其它初始化器(这里是继承父类的),然后再为任意属性设置新值
convenience init(titles: [String] = [], vcs: [UIViewController] = [], pageStyle: XUPageStyle = .noneStyle) {
self.init()
self.titles = titles
self.vcs = vcs
self.pageStyle = pageStyle
}
③ required
- 用required修饰指定初始化器(修饰便捷初始化器没啥用),表明其所有子类都必须实现该初始化器(通过继承或者重写实现)
- 如果子类重写了required初始化器,也必须加上required,不用加override
class Person {
required init() { }
init(age: Int) { }
}
class Student : Person {
required init() {
super.init()
}
}
④ 属性观察器
父类的属性在它自己的初始化器中赋值不会触发属性观察器,但在子类的初始化器中赋值会触发属性观察器
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本来就是从父类来的,你修改了父类的属性,肯定要触发父类的属性观察器。
4. 可失败初始化器、反初始化器
① 可失败初始化器
枚举、结构体、类都可以使用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就创建失败,所以是可失败的
- 不允许同时定义参数标签、参数个数、参数类型相同的可失败初始化器和非可失败初始化器(因为不知道调用哪个了)
- 可以用init!定义隐式解包的可失败初始化器
- 可失败初始化器可以调用非可失败初始化器,非可失败初始化器调用可失败初始化器需要进行解包
- 如果初始化器调用一个可失败初始化器导致初始化失败,那么整个初始化过程都失败,并且之后的代码都停止执行
- 可以用一个非可失败初始化器重写一个可失败初始化器,但反过来是不行的。
② 反初始化器(deinit)
deinit叫做反初始化器,类似于C++的析构函数、OC中的dealloc方法
当类的实例对象被释放内存时,就会调用实例对象的deinit方法
class Person {
deinit {
print("Person deinit")
}
}
- deinit不接受任何参数,不能写小括号
- 父类的deinit能被子类继承
- 子类的deinit实现执行完毕后会调用父类的deinit
如下,子类没有实现deinit,通过打印可知,子类继承了父类的deinit
class Person {
deinit {
print("Person deinit")
}
}
class Student: Person {
}
func test() {
var stu = Student()
}
test() //打印:Person deinit