重写
override
-
重写类型方法、下标
- 被
class
修饰的类型方法、下标,允许被子类重写 - 被
static
修饰的类型方法、下标,不允许被子类重写
注意:如果此时继承父类重写父类方法之后,如果重写的方法不想继续被子类去重写,那么可以此时在重写的方法前面添加static关键字修饰来确保子类不能够重写当前的方法
- 被
class AnimalSubscript {
func speak() {
print("animal speak")
}
subscript(index: Int) -> Int {index}
class func eat() {
print("animal eat")
}
static func drink() {
print("animal drink")
}
}
class Cat: AnimalSubscript {
override func speak() {
print("cat speak")
}
override subscript(index: Int) -> Int {index + 20}
override class func eat() {
print("cat eat")
}
//此时重写的方法将不能被子类重写修改
// override static func eat(){
// print("终结当前的方法")
// }
}
- 重写属性
- 子类可以将父类的
计算属性、存储属性
重写为计算属性
- 子类不可以将父类重写为
存储属性
- 只能重写
var
变量属性。不能重写let
属性 - 重写时,属性名、类型要一致
- 子类重写后的
属性权限
不能小于父类属性的权限
- 如果父类属性是只读的,那么子类重写后的属性可以是
只读
的,也可以是可读写
的 - 如果父类属性是
可读可写
的,那么子类重写后的属性也必须是可读写的
- 子类可以将父类的
class OverrideCircle {
//懒加载
lazy var lazyVar: Int = {
return 1
}()
private(set) var onlyRead: Int = 0
var radius: Int = 0
var diameter: Int{
get {
print("get diameter")
return (radius * 2)
}
set {
radius = newValue
print("Circle set diameter")
}
}
}
class OverrideCircleSon: OverrideCircle {
var newRadius = 0
override var diameter: Int {
get {
print("overrideCircleSon get value")
return radius * 2
}
set {
print("overrideCircleSon set value")
radius = newValue
}
}
override var radius: Int{
get {
print("reWrite stored perproty of radius")
return newRadius
}
set {
print("reGet sotre perproty of radius")
newRadius = newValue
}
}
}
- 属性观察器
- 可以在子类为父类属性(除了只读计算属性、
let属性
)增加属性观察器 -
set 与willset
、get 与 willget
均不能共存的 - 不管父类是实例属性,类型属性或者存储属性,计算属性,子类都可以为父类添加观察属性
- 可以在子类为父类属性(除了只读计算属性、
class perprotyObserverClass: OverrideCircle{
override var radius: Int{
willSet {
print("will set value", newValue)
}
didSet {
//此时的radius不会引发死循环,因为此时并没有重写计算属性,此时访问的仍旧是radius本身,
//属性监听器的实现是通过中间临时变量来实现的,所以不存在死循环
print("set value finish", oldValue, radius)
}
}
//不能够正常添加观察期监听
// override var onlyRead: Int{
// willSet {
//
// }
// }
override var lazyVar: Int{
willSet {
print("lazy load will set")
}
}
}
- final 使用
- 被
final
修饰的方法、下标、属性
,禁止被重写 - 被
final
修饰的类,禁止被继承
- 被
final class finalClass {
final var radius: Int {
set {
print("radius write")
}
get {
print("circle get radius")
return 20
}
}
}
多态
- 多态是指:父类指针指向子类对象的行为现象
- 在OC中多态是依靠runtime来实现的,而Swift中的实现方式类似于C++中的虚函数表来实现的
几个问题:
- 父类是如何判断对应的子类对象的类型的(多态)
- 如果将
class
类型换成struct
类型,那么在内存中调用的时候有什么区别?
因为对于结构体来说,存储在栈空间
,所以存储的地址在编译期间
就已经确定。而对象的实例,需要堆空间去动态分配内存
,此时的堆空间的地址是动态分配
的,由此调用对应的方法由于对象实例分配动态的原因,并不能直接在编译期间
直接找到其内存地址,所以需要在运行的时候去找到动态分配的地址空间
,进而去通过该地址间接找到要调用的方法地址,因此对象的方法的调用地址
是变化的。所以相比于类调用方法,结构体调用的方法执行效率要比放在类中执行的效率高很多
初始化器
-
特点
-
类、结构体、枚举
都可以定义初始化器 - 类有两种初始化器:指定初始化器(designed initializer)、便捷初始化器(convenience initializer)
- 每个类至少有一个
初始化器
,指定初始化器是类的主要初始化器 - 默认初始化器总是类的指定初始化器
- 类偏向于少量的指定初始化器,一个类通常只有一个指定初始化器
- 初始化过程(为了保证初始化安全,设定了
两段式初始化
、安全检查
)
-
-
初始化器的相互调用规则
- 指定初始化器必须从它的直系父类调用指定初始化器
- 便携初始化器必须从相同的类里调用另一个初始化器
- 便携初始化器最终必须调用一个指定初始化器
class Person {
var name: String
init(name: String) {
self.name = name
}
}
class Student: Person {
var score: Int
var height: Double
//指定初始化器
init(score: Int, height: Double) {
self.score = score
self.height = height
super.init(name: "student")
}
//便携初始化器
convenience init(score: Int) {
self.init(score:99, height: 20.0)
self.score = score
}
//重写父类的指定初始化器为便捷初始化器
convenience override init(name: String) {
self.init(score: 98, height: 177)
}
}
-
两段式初始化过程
初始化所有的存储属性
1. 外层调用指定/便携初始化器
2. 分配内存给实例,但未初始化
3. 指定初始化器来确保当前类定义的存储属性都初始化
4. 指定初始化器调用父类的初始化器,不断向上调用,形成初始化器链设置新的存储属性值
1. 从顶部初始化器往下,链中的每一个指定初始化器都有机会进一步定制实例
2. 初始化器现在能够使用self(访问、修改它的属性,调用它的实例方法等等)
3. 最终,链中的任何便捷初始化器都有机会定制实例以及使用self-
如果存在继承关系,初始化顺序如下:
- 先初始化指定初始化器完成子类的初始化
- 依次调用父类的指定初始化器完成初始化操作
安全检查
1. 指定初始化器必须保证在调用父类初始化之前,其所在类定义的所有存储属性都要初始化完成
2. 指定初始化器必须先调用父类初始化器,然后才能为继承的属性设置新值
3. 便捷初始化器必须先调用同类中的其他初始化器,然后再为任意属性设置新值
4. 初始化器在第一阶段初始化完成之前,不能调用任何实例方法,不能读取任何实例属性的值,也不能引用self
5. 直到第一阶段完成,实例才能算完全合法重写父类的指定初始化器
1. 必须加上override
(即使子类实现的是便捷初始化器)
2. 因为父类的便捷初始化器永远不会通过子类直接调用,所以,子类无法重写父类的便捷初始化器
-
自动继承
- 特点
- 如果子类没有定义任何
初始化器
,那么它将自动继承父类所有的指定初始化器
- 如果子类提供了父类所有指定初始化器的实现(一种是
全部重写
、另一种是通过步骤1
来实现),此时子类将会自动继承
所有的父类的便捷初始化器
- 就算子类添加很多
便捷初始化器
,以上特点依旧适用 - 子类用
便捷初始化器
的形式重写父类的指定初始化器
,此时步骤2
也适用于这种情况
-
required
使用- 特点
- 用
required
修饰指定初始化器,表明其所有子类都必须实现该初始化器(通过继承或者重写来实现) - 如果子类重写了required初始化器,也必须加上required,不用加override
初始化引发的属性观察器的变化
特点:
1. 父类的属性在它自己的初始化器中赋值不会触发属性观察器
2. 在子类的初始化器中赋值会触发属性观察器
class Animal {
var type: String {
didSet {
print("oldValue:\(oldValue), new value:\(type)")
}
willSet {
print("newValue:\(newValue)")
}
}
required init(type: String) {
self.type = type
}
}
class Dog: Animal {
var age: Int
init(age: Int) {
self.age = age
super.init(type: "dog")
}
required init(type: String) {
self.age = 0
super.init(type: "dog")
self.type = "pig"
}
}
-
反初始化器
deinit
- 类似于
C++
的析构函数、OC中的dealloc
方法 - 当类的实例对象被释放内存的时候,会调用实例对象的
deinit
方法 -
deinit
不能接受任何参数、不能写小括号、不能自行调用 - 父类的
deinit
能被子类继承 - 子类的
deinit
实现执行完毕后,会调用父类的deinit
- 类似于
-
可失败初始化器
特点:-
类、结构体、枚举
都可以使用init?
定义可失败初始化器
- 不允许同时定义参数标签、参数个数、参数类型相同的
可失败初始化器
与非可失败初始化器
-
可失败初始化器
可以调用非可失败初始化器
,非可失败初始化器
调用可失败初始化器
需要进行解包操作
- 如果初始化器调用一个可失败初始化器导致
初始化失败
,那么整个初始化过程都会失败
,并且之后的代码都会停止
- 可以用一个
非可失败初始化器
重写一个可失败初始化器
,但反过来是不行的 - 可以通过可选链来间接的进行判空容错(如果为nil,不执行后序语句内容)
-
class PersonCanFailed {
var name: String
var age: Int?
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
//定义一个便捷初始化器
convenience init?() {
self.init(name: "leo") //如果初始化失败,后序代码将不会再执行
self.age = 10
}
//已经接触到的可失败初始化器
func demo() {
//可能初始化失败
let _ = Int("12345555")
enum Answer: Int{
case wrong, right
}
//可能初始化失败
let _ = Answer(rawValue: 1)
}
}
func testOptinolChainUse() {
var scores = ["Jack": [89, 99, 67],
"Rose": [89, 99, 67]]
scores["Jack"]?[0] = 100
scores["Rose"]?[2] = 87
//判断下面num1与num2 内容是什么类型
var num1: Int? = 5
num1? = 10 //Optinoal(10)
//num2? 等同于判定num2是否为空,如果为空后序赋值操作将取消,如果不为空则正常操作
var num2: Int? = nil
num2? = 10 //nil
num2 = 19 //19
}
func testOptionalChainDictUse() {
var dict:[String: (Int, Int) -> Int] = ["sum":(+), "difference":(-)]
var result = dict["sum"]?(10, 20) //Optional(30), Int?
}
可选链
?
-
可选链调用特点
-
?
的作用是判断调用者是否有值,有的话会继续调用后序方法,没有的话,直接返回,返回的内容是可选类型
(这个行为改变了原有的返回数据类型
,将之前返回的内容包装成可选类型
了) - 方法没有返回值却能够使用
var
进行接收,原因是方法的调用默认返回值是Void
,可以认定是一个空元组类型
- 可以通过
可选链
接收返回值的方法来判定方法是否调用成功
-
-
其他需要注意的点
- 如果可选为
nil
,调用方法、下标、属性
失效,结果为nil
- 如果可选项不为
nil
,调用方法、下标、属性成功
,结果会被包装成可选类型
- 如果结果本来就是
可选项
,不会进行再包装 - 多个
?
可选可以链接在一起 - 如果可选链中任何一个节点是
nil
,那么整个可选链
就会调用失败
- 如果可选为
func testOptionlUse() {
var person: CarPerson? = CarPerson()
var age = person?.age() //可选链调用
var age1 = person!.age() //强制解包
var name = person?.name //可选链调用
var result = person?.eat() //没有返回值仍能够通过变量去接受返回值
if let _ = person?.eat() {
print("调用eat成功")
}else{
print("调用eat失败")
}
}