Swift 协议 (Protocol)
什么是协议?
协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
下面是协议的注意点
- 协议中定义方法时不能有默认参数值
- 默认情况下,协议中定义的内容必须全部都实现
- 也有办法办到只实现部分内容,以后的课程会讲到
下面是单个协议的写法
// 基础协议定义
protocol Drawable {
func draw()
var x: Int { get set }
var y: Int { get }
subscript(index: Int) -> Int { get set }
}
遵守多个协议下面的写法
protocol Test1 {}
protocol Test2 {}
protocol Test3 {}
class TestClass : Test1, Test2, Test3 {}
协议中的属性
- 协议中定义属性时必须用var关键字.个人理解协议定义的是"能力",不是"存储方式"。var表示"可访问",let表示"不可变存储"。
- 实现协议时的属性权限要不小于协议中定义的属性权限。访问控制级别: private < fileprivate < internal < public < open
- 协议定义get、set,用var存储属性或get、set计算属性去实现
- 协议定义get,用任何属性都可以实现
protocol Drawable {
func draw()
var x: Int { get set } // 读写属性
var y: Int { get } // 只读属性
subscript(index: Int) -> Int { get set } // 读写下标
}
下面是第一种实现方式
class Person : Drawable {
var x: Int = 0 // var存储属性实现读写
let y: Int = 0 // let常量实现只读
func draw() { print("Person draw") }
subscript(index: Int) -> Int {
set {}
get { index }
}
}
下面是第二种实现方式
class Person : Drawable {
var x: Int { get { 0 } set {} } // 计算属性实现读写
var y: Int { 0 } // 计算属性实现只读
func draw() { print("Person draw") }
subscript(index: Int) -> Int {
set {}
get { index }
}
}
协议中的static 和 class
协议中必须使用static,而不能使用class,然后你在具体的协议的实现中,如果你是结构体或者枚举,那么可以使用static,而你在类里面既可以使用class,又可以使用static。而在类里面使用static的方法,子类不可以override,而class的方法子类可以重写。
protocol Drawable {
static func draw() // 协议中必须用static
}
// 方式1:使用class func(可重写)
class Person1 : Drawable {
class func draw() {
print("Person1 draw")
}
}
// 方式2:使用static func(不可重写)
class Person2 : Drawable {
static func draw() {
print("Person2 draw")
}
}
协议中的mutating
首先我们要搞明白mutating的作用,mutating关键字用于标记那些会修改实例自身属性的方法,主要针对值类型(结构体、枚举)。其实就是说这些是值类型,但是会在这个方法里面修改他们的内存,这个时间你这个方法要加上mutating。 还是和前面的类似,由于这个协议你有可能给结构体,枚举,类使用,里面修改自身的内容,以防万一你给值类型用,还是加上mutating吧。协议里面加了,到结构体,枚举的这个方法实现里面也需要加,而类不需要加。
protocol Drawable {
mutating func draw() // 协议中标记为mutating
}
// 类实现 - 不需要mutating
class Size : Drawable {
var width: Int = 0
func draw() { // 注意:没有mutating
width = 10
}
}
// 结构体实现 - 需要mutating
struct Point : Drawable {
var x: Int = 0
mutating func draw() { // 注意:必须有mutating
x = 10
}
}
协议中的init方法
- 协议可以定义init方法
- 非final类实现时必须加上required
protocol Drawable {
init(x: Int, y: Int)
}
// 非final类 - 需要required
class Point : Drawable {
required init(x: Int, y: Int) { // 必须加required
// 实现代码
}
}
// final关键字用于标记类、方法、属性或下标,表示它们不能被继承或重写。
// final类 - 不需要required
final class Size : Drawable {
init(x: Int, y: Int) { // 不需要required
// 实现代码
}
}
- 如果从协议实现的初始化器,刚好是重写了父类的指定初始化器
- 那么这个初始化必须同时加required、override
protocol Livable {
init(age: Int)
}
class Person {
init(age: Int) { // 父类的指定初始化器
// 父类实现
}
}
class Student : Person, Livable {
required override init(age: Int) { // 同时需要required和override
super.init(age: age)
}
}
协议的继承
一个协议可以继承其他协议
protocol Runnable {
func run()
}
protocol Livable : Runnable { // 协议继承
func breath()
}
class Person : Livable { // 类遵循协议
func breath() { }
func run() { }
}
协议的组合
protocol Livable { }
protocol Runnable { }
class Person { }
// 只接收Person类或其子类
func fn0(obj: Person) { }
// 只接收遵循Livable协议的类型
func fn1(obj: Livable) { }
// 接收同时遵循Livable和Runnable协议的类型
func fn2(obj: Livable & Runnable) { }
// 接收Person类且同时遵循Livable和Runnable协议的类型
func fn3(obj: Person & Livable & Runnable) { }
// 定义类型别名
typealias RealPerson = Person & Livable & Runnable
// 使用类型别名
func fn4(obj: RealPerson) { }
两个系统的协议
CaseIterable 协议
让枚举自动生成一个名为 allCases 的静态集合,包含该枚举的所有 case,便于遍历、计数等。
enum Season: CaseIterable {
case spring, summer, autumn, winter
}
let seasons = Season.allCases // [spring, summer, autumn, winter]
print(seasons.count) // 4
for s in seasons { print(s) } // 逐个遍历
限制:仅适用于无关联值的枚举(rawValue 可以有,不影响)。
如果有不可用的 case(如用 @available 标注),那些 case 不会出现在 allCases 中。
CustomStringConvertible / CustomDebugStringConvertible
- CustomStringConvertible: 自定义“用户可读”的字符串。print(x) 或 String(describing: x) 会用 description。
- CustomDebugStringConvertible: 自定义“调试用”的字符串。debugPrint(x)、LLDB po x 或 String(reflecting: x) 会用 debugDescription
final class Person: CustomStringConvertible, CustomDebugStringConvertible {
var age = 0
var description: String { "person_\(age)" } // 给 print 看
var debugDescription: String { "debug_person_\(age)" } // 给 debugPrint/po 看
}
let p = Person()
print(p) // person_0
debugPrint(p) // debug_person_0
Any 与 AnyObject
- Any: 可以表示任意类型(枚举、结构体、类、函数、闭包、元组等)。
- AnyObject: 可以代表任意类类型(在协议后面写上: AnyObject代表只有类能遵守这个协议),在协议后面写上: class也代表只有类能遵守这个协议
var x: Any = 10
x = "Jack"
x = { (a: Int) in a * 2 } // 函数/闭包也可以
var arr: [Any] = []
arr.append(1)
arr.append(3.14)
arr.append("hello")
arr.append(Student())
arr.append({ 10 })
X.self、X.Type、AnyClass
X.self是一个元类型(metadata)的指针,metadata存放着类型相关信息 X.self属于X.Type类型
class Person {}
class Student: Person {}
// 1) 取类型对象
let pMeta: Person.Type = Person.self
let sMeta: Student.Type = Student.self
// 2) 子类类型对象可以赋给父类的 metatype(协变)
let covar: Person.Type = Student.self // OK
其实这里可以简单理解是 Person.self 这个获取的是Person这前8个字节,前八个字节存储的是类型信息。 Person.self 获取的数据类型是Person.Type。其实self 很类似于 oc中class。
typeof
let p = Person()
let t = type(of: p) // 等价于使用Person.self,最终的类型是Person.Type
print(Person.self == type(of: p)) // true
元类型的应用1
class Animal { required init() {} }
class Cat: Animal {}
class Dog: Animal {}
class Pig: Animal {}
func create(_ clses: [Animal.Type]) -> [Animal] {
var arr = [Animal]()
for cls in clses {
arr.append(cls.init()) // 运行时用类型对象构造对应子类
}
return arr
}
let animals = create([Cat.self, Dog.self, Pig.self])
元类型的应用2
import Foundation
class Person { var age: Int = 0 }
class Student: Person { var no: Int = 0 }
print(class_getInstanceSize(Student.self)) // 32(64 位环境下)
print(class_getSuperclass(Student.self)!) // Person
print(class_getSuperclass(Person.self)!) // Swift._SwiftObject
Self
- Self(大写)= 当前类型本身
- 在实例方法里:Self 指向“声明该方法的类型”。
- 在类型上下文里:Self 等同于该类型(可访问类型属性/方法)。
- 区分:self(小写)是“当前实例”或“当前类型对象”。
class Person {
var age = 1
static var count = 2
func run() {
print(self.age) // 实例成员 -> 1
print(Self.count) // 类型成员 -> 2(Self 指 Person)
}
}
Self一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)
protocol Runnable {
func test() -> Self
}
class Person: Runnable {
required init() {}
func test() -> Self { type(of: self).init() }
}
class Student: Person {}
let p = Person()
print(type(of: p.test())) // Person
let s = Student()
print(type(of: s.test())) // Student(同型返回)
上面的例子test的方法为啥必须写成Self呢?是因为student,person都可能调这个方法,而不能写死成student或者person.