协议(Protocol)
- 协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
- 协议中定义方法时不能有默认参数值
- 默认情况下,协议中定义的内容必须全部都实现
- 也有办法可以办到只实现部分内容
协议中的属性
- 协议中定义属性时必须用var关键字
- 实现协议时的属性权限要不小于协议中定义的属性权限
- 协议定义get、set,用var存储属性或get、set计算属性去实现
- 协议定义get,用任何属性都可以实现
这里的var x: Int = 0可以看作是实现了set和get,因为可以赋值和取值;let y: Int = 0由于不能修改值,可以看作只实现了get,也可以使用下图的计算属性方式实现:
static、class
为了保证通用,协议中必须用static定义类型方法、类型属性、类型下标
实现的时候使用class则代表方法可以被继承,static则代表方法不能被继承
mutating
- 只有将协议中的实例方法标记为mutating,才允许结构体、枚举的具体实现修改自身内存
- 类在实现方法时不用加mutating,枚举、结构体才需要加mutating
protocol中不加mutating,而结构体里加了mutating,结构体会报错,protocol对于结构体而言需要加mutating
init
- 协议中还可以定义初始化器init
- 非final类实现时必须加上required
加required的原因是因为这样可以要求继承自Point的类都必须实现init,从而达到所有遵守Drawable协议的类都实现init,final不需要加是因为加final的类不允许被继承。
- 如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化必须同时加required、override
init、init?、init!
- 协议中定义的init?、init!,可以用init、init?、init!去实现
- 协议中定义的init,可以用init、init!去实现
protocol Liveable {
init()
init?(age: Int)
init!(no: Int)
}
class Person: Liveable {
required init() {
}
//init()第二种写法
// required init!() { }
required init?(age: Int) {
}
//init?(age: Int)第二种写法
// required init(age: Int) { }
// required init!(age: Int){ }
required init!(no: Int) {
}
//init!(no: Int)第二种写法
// required init(no: Int){ }
// required init?(no: Int){ }
}
协议的继承
- 一个协议可以继承其他协议
可以看出,遵守Livable协议的Person类必须实现Runnable和Livable都有的方法。
协议组合
- 协议组合,可以包含一个类类型(最多一个)
可以给协议组合起别名:
CaseIterable
- 让枚举遵守CaseIterable协议,可以实现遍历枚举值
CustomStringConvertible
- 遵守CustomStringConvertible协议,可以自定义实例的打印字符串
- print调用的是CustomStringConvertible协议的description
- debugPrint,po调用的是CustomDebugStringConvertible协议的debugDescription
Any、AnyObject
- Swift提供了2种特殊的类型:Any、AnyObject
- Any:可以代表任意类型(枚举、结构体、类,也包括函数类型)
- AnyObject:可以代表任意类类型(在协议后面写上: AnyObject代表只有类能遵守这个协议)
stu属于Any类型,可以赋任何值:
[Any]():创建可以存放任何类型的数组
is、as?、as!、as
- is用来判断是否为某种类型,as用来做强制类型转换
is判断类型:
as做强制类型转换:
(stu as? Student)?.study():第一个?代表stu可能强制转化为Student,也可能失败,第二个?代表可选类型要调用方法,需要加?
(stu as? Student)!.study() 等同于 (stu as! Student).study()
当确定一定会转换成功用as,否则用as?
Int("123")一定可以转化为Any,所以用as
10一定可以转换为Double,所以用as
X.self、X.Type、AnyClass
- X.self是一个元类型(metadata)的指针,metadata存放着类型相关信息
对象的指针指向的堆内存的前8个字节存放着类型相关信息,可以把前8个字节认为是元类型的指针,指向元类型metadata
class Person {
}
var p: Person = Person()
var pType: Person.Type = Person.self
反汇编上面代码,查看pType存储的到底是什么:
根据两个断点处的rax对比我们可以看出,代表p对象内存地址的第二个rax中的前8个字节与代表pType的第一个rax相同,说明pType存储的是p对象的metadata。
- X.self属于X.Type类型
由上图看出,由于Student继承自Person,所以pType = Student.self也成立,好比多态的父类指针指向子类对象,但是不继承自Person的类不可以这么写
- 在Swift底层方法里,有这么个定义
代表着AnyClass就是AnyObject.Type,是任意类类型的Type
等价于
- type(of: )获取元类型的指针
元类型的应用
为什么Animal的init(){ }要用required修饰?因为这是要求子类必须有的,实现的方法,万一子类重写init相关的方法,就会造成子类里不会自动继承父类的init,这时添加required,是强制子类实现init()方法,这样就会保证子类可以调用init()方法
class_getInstanceSize:表示对象实际用到的内存大小,也就是存储属性需要的最小值
class_getSuperclass:获取父类,我们可以看到Person虽然没继承任何类,但它有一个要隐藏的基类Swift._SwiftObject
- 从结果可以看得出来,Swift还有个隐藏的基类:Swift._SwiftObject
- 可以参考Swift源码:https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.h
Self
- Self代表当前类型
Self.count 等同于Person.count
- Self一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)
protocol Runnable {
func test () -> Self
}
class Person : Runnable {
required init() { }
func test() -> Self {
return type(of: self).init()
}
}
class Student: Person { }
var p = Person()
//TestSwift.Person
print(p.test())
var stu = Student()
//TestSwift.Student
print(stu.test())