Files
knowledge-kit/Chapter1 - iOS/1.117.md
2024-04-27 13:01:58 +08:00

7.1 KiB
Raw Blame History

Swift 协议探究

  • 协议可以用来定义属性、方法、下标的声明,协议可以被类、枚举、结构体遵守(多个协议用逗号隔开)

  • 协议中定义的方法不能有默认参数值

  • 协议中定义属性必须是 var

  • 实现协议时定义的属性权限,要不小于协议中定义的属性权限

  • 协议定义属性是 getset 时,用 var 存储属性或者 getset 计算属性实现

  • 为了保证通用,协议中必须用 static 定义类型方法、类型属性、类型下标

  • 只有将协议中的实例方法标记为 mutating

    • 才可以允许结构体、枚举对象在方法里修改自身内存。否则编译器会报错:Cannot assign to property: 'self' is immutable
    • 类遵循协议,实现方法的时候不用加 mutating,枚举、结构体的实现需要加 mutating
    protocol Drawable {
        func draw()
    }
    
    class Size: Drawable {
        var width: Int = 0
        func draw() {
            width = 10
        }
    }
    var size = Size()
    print(size.width)	// 0
    size.draw()
    print(size.width) // 10
    
    struct Point: Drawable {
        var x : Int = 0
        var y: Int = 0
        func draw() {
            x = 10	// Cannot assign to property: 'self' is immutable
            y = 10	// Cannot assign to property: 'self' is immutable
        }
    }
    

    要想修改需要加 mumating

    struct Point: Drawable {
        var x : Int = 0
        var y: Int = 0
        mutating func draw() {
            x = 10
            y = 10
        }
    }
    var point = Point()
    print(point.x, point.y)
    point.draw()
    print(point.x, point.y)
    
  • 协议中还可以定义初始化器 init,非 final 类实现协议时, init 方法必须加 required

    protocol Drawable {
        init(x: Int, y: Int)
    }
    class Point: Drawable {
        var x: Int = 0
        var y: Int = 0
        required init(x: Int, y: Int) {
            self.x = x
            self.y = y
        }
    }
    final class Size: Drawable {
        var x: Int = 0
        var y: Int = 0
        init(x: Int, y: Int) {
            self.x = x
            self.y = y
        }
    }
    
    var point = Point(x: 10, y: 20)
    print(point.x , point.y) // 10 20
    var size = Size(x: 30, y: 40)
    print(size.x , size.y) // 30 40
    
  • 如果协议声明了初始化器,某个类遵循协议并实现了初始化器。且该初始化器也恰好是父类指定初始化器,那么这个初始化必须同事加 requiredoverride

    protocol Drawable {
        init(x: Int, y: Int)
    }
    
    class Shape {
        init(x: Int, y: Int) {}
    }
    
    class Circle: Shape, Drawable {
        var x: Int = 0
        var y: Int = 0
        required override init(x: Int, y: Int) {
            super.init(x: x, y: y)
            self.x = x
            self.y = y
        }
    }
    var circle = Circle(x: 10, y: 20)
    print(circle.x , circle.y) // 10 20
    
  • 协议也可以继承

  • 协议也可以组合

    protocol Drawable {}
    protocol Colorable {}
    func test1(obj: Shape) {} 								// 参数接收 Shape 类或者 Shape 类的子类
    func test2(obj: Drawable) {} 							// 参数接收遵循 Drawable 的实例
    func test3(obj: Drawable & Colorable) {}	 // 参数接收同时遵循 Colorable 和 Drawable 2个协议的实例
    func test4(obj: Shape & Drawable & Colorable) {} // 参数接收同时遵循 Colorable 和 Drawable 2个协议且是 Shape 的子类的实例
    
  • 遵循 CustomStringConvertible 可以自定义打印的字符串内容

    class Person: CustomStringConvertible {
        var name: String
        var age: Int
        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }
        var description: String {
            "My name is \(name), age is \(age)"
        }
    }
    var p = Person(name: "杭城小刘", age: 28)
    print(p) // My name is 杭城小刘, age is 28 
    

Any、AnyObject

Swift 提供了2种特殊的类型Any、AnyObject

  • Any 可以代表任意类型(枚举、结构体、类、函数类型)
  • AnyObject代表任意类类型。比如可以在协议后面加上 AnyObject 则代表只有类能遵循这个协议。编译器会做检查 Non-class type 'point' cannot conform to class protocol 'Eatable'
protocol Eatable: AnyObject {}
class Person: Eatable { }
struct point: Eatable {} // Non-class type 'point' cannot conform to class protocol 'Eatable'

关联类型

关联类型的作用:给协议中用到的类型,定义一个占位名称

协议中可以拥有多个关联类型

protocol Stackable {
    associatedtype Element
    mutating func push(_ element: Element)
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}

class Stack<Element>: Stackable {
    var elements = Array<Element>()
    func push(_ element: Element) {
        elements.append(element)
    }
    func pop() -> Element {
        elements.removeLast()
    }
    func top() -> Element {
        elements.last!
    }
    func size() -> Int {
        elements.count
    }
}

Swift 范型本质

func swapValue<T>(_ value1: inout T, _ value2: inout T) {
    (value1, value2) = (value2, value1)
}
var i1 = 11
var i2 = 22
swapValue(&i1, &i2)

var s1 = "Hello"
var s2 = "world"
swapValue(&s1, &s2)

swapValue(&i1, &i2) 处下断点可以看到下面的汇编代码:

可以看到:

  • 在第一处调用 swapValue 方法的时候将8字节的 metadata 信息保存到 rdx 寄存器了。也就是在调用 swapValue 方法的时候,分别将 i10x000000000000000b也就是11的地址值赋值给 rdi 寄存器,将 i20x0000000000000016也就是22的地址值赋值给 rsi 寄存器
  • Intmetadata 赋值给 rdx 寄存器
  • 然后调用 swapValue 方法
  • 后续的 StringSwapValue 过程类似

所以编译器最后在执行的时候,会将范型真正的类型对应的 metadata 信息当作函数参数,传递进去,再去执行函数

范型类型约束

范型必须遵循协议,可以在方法后加 <>,在 <> 内写范型 T: 需要继承的类 & 协议 (也可以是其他名字,大多数语言都写 T

protocol Runable {}
class Person {}
func swapValue<T: Person & Runable>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}

另一种场景是在方法参数是某个范型且遵循协议后,对其范型有更多限制,则用 where 去处理。如下例子

protocol Stackable {
    associatedtype Element: Equatable
}

class Stack<E: Equatable>: Stackable {
    typealias Element = E
}

func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2)-> Bool
where S1.Element == S2.Element, S1.Element: Hashable {
    return true
}

let s1 = Stack<Int>()
let s2 = Stack<Int>()
let s3 = Stack<String>()
var result:Bool = equal(s1, s2)
print(result) // true
result = equal(s1, s3) // Global function 'equal' requires the types 'Stack<Int>.Element' (aka 'Int') and 'Stack<String>.Element' (aka 'String') be equivalent
print(result)