Files
knowledge-kit/Chapter1 - iOS/1.117.md
2025-06-23 01:18:55 +08:00

267 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Swift 协议探究
- 协议可以用来定义属性、方法、下标的声明,协议可以被类、枚举、结构体遵守(多个协议用逗号隔开)
- 协议中定义的方法不能有默认参数值
- 协议中定义属性必须是 `var`
- 实现协议时定义的属性权限,要不小于协议中定义的属性权限
- 协议定义属性是 `get``set` 时,用 `var` 存储属性或者 `get``set` 计算属性实现
- 为了保证通用,协议中必须用 `static` 定义类型方法、类型属性、类型下标
- 只有将协议中的实例方法标记为 `mutating`
- 才可以允许结构体、枚举对象在方法里修改自身内存。否则编译器会报错:`Cannot assign to property: 'self' is immutable`
- 类遵循协议,实现方法的时候不用加 `mutating`,枚举、结构体的实现需要加 `mutating`
```swift
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`
```swift
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`
```swift
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
```
- 如果协议声明了初始化器,某个类遵循协议并实现了初始化器。且该初始化器也恰好是父类指定初始化器,那么这个初始化必须同事加 `required` 和 `override`
```swift
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
```
- 协议也可以继承
- 协议也可以组合
```swift
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` 可以自定义打印的字符串内容
```swift
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'`
```swift
protocol Eatable: AnyObject {}
class Person: Eatable { }
struct point: Eatable {} // Non-class type 'point' cannot conform to class protocol 'Eatable'
```
## 关联类型
关联类型的作用:给协议中用到的类型,定义一个占位名称
协议中可以拥有多个关联类型
```swift
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 泛型本质
```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)` 处下断点可以看到下面的汇编代码:
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftGenericExploreDemo1.png" style="zoom:25%">
可以看到:
- 在第一处调用 `swapValue ` 方法的时候将8字节的 metadata 信息保存到 `rdx` 寄存器了。也就是在调用 `swapValue` 方法的时候,分别将 `i1`0x000000000000000b也就是11的地址值赋值给 rdi 寄存器,将 `i2`0x0000000000000016也就是22的地址值赋值给 rsi 寄存器
- 将 `Int` 的 `metadata` 赋值给 `rdx` 寄存器
- 然后调用 `swapValue` 方法
- 后续的 `String` 的 `SwapValue` 过程类似
所以编译器最后在执行的时候,会将泛型真正的类型对应的 metadata 信息当作函数参数,传递进去,再去执行函数
## 泛型类型约束
泛型必须遵循协议,可以在方法后加 `<>`,在 `<>` 内写泛型 `T: 需要继承的类 & 协议` (也可以是其他名字,大多数语言都写 T
```swift
protocol Runable {}
class Person {}
func swapValue<T: Person & Runable>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
```
另一种场景是在方法参数是某个泛型且遵循协议后,对其泛型有更多限制,则用 `where` 去处理。如下例子
```swift
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)
```