mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
267 lines
7.1 KiB
Markdown
267 lines
7.1 KiB
Markdown
# 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)
|
||
```
|
||
|