14.Swift 协议 (Protocol)

119 阅读6分钟

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 {}

协议中的属性

  1. 协议中定义属性时必须用var关键字.个人理解协议定义的是"能力",不是"存储方式"。var表示"可访问",let表示"不可变存储"。
  2. 实现协议时的属性权限要不小于协议中定义的属性权限。访问控制级别: private < fileprivate < internal < public < open
  3. 协议定义get、set,用var存储属性或get、set计算属性去实现
  4. 协议定义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

  1. CustomStringConvertible: 自定义“用户可读”的字符串。print(x) 或 String(describing: x) 会用 description。
  2. 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.