Swift 中的协议、泛型、不透明类型

在学习 SwiftUI 的过程中,发现对标题中的内容还不甚熟悉,而这些内容是 SwiftUI 中极其重要的部分,不理解就很难熟练地掌握 SwiftUI,故温习并记录,以下内容可视为官方教程的简装版。

协议

定义

协议规定了实现某一特定功能的方法和属性。

类、结构体、枚举类型都可以遵循协议。

语法

1
2
3
protocol SomeProtocol {
// 协议内容
}

属性要求

属性可以是存储型或计算型,必须明确其可读写性。

1
2
3
4
5
protocol Cat {
var food: String { get set } // 可读写
var color: Color { get } // 只读
static var age: Int { get } // static 用于 class 的类型属性
}

方法要求

方法可以是实例方法或类方法,方法参数不能使用默认值。

异变方法要求

协议中的实例方法可以标记为 mutating,当结构体、枚举这样的值类型实现相应的方法时,需要加上 mutating 关键字,而类作为引用类型,不需要该关键字。

初始化器要求

协议可以要求遵循者实现指定的初始化器,只是不需要写初始化器的实体,即大括号里的内容。

1
2
3
4
protocal Cat {
init(from: String)
init?(color: Color) // 可失败的初始化器
}

协议初始化器要求的类实现

在遵循协议的类中实现的构造器,可以指定为类的指定构造器或便利构造器,在这两种情况下,必须使用 required 修饰构造器。该修饰符保证:所有遵循该协议的子类,都能有一个明确的继承实现。

1
2
3
4
5
6
7
8
9
10
11
12
protocol P {
init()
}

class SomeSuperClass {
init() {}
}

class SomeSubClass: SomeSuperClass, P {
// SomeSubClass 遵循协议 P,所以要用 required 修饰
required override init {}
}

将协议作为类型

使用场合:

  • 在函数、方法或者初始化器里作为形参类型或者返回类型
  • 作为常量、变量或者属性的类型
  • 作为数组、字典或者其他存储器的元素的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
protocol LifeStyle {
func lifeStyle() -> [String]
}

class Person {
var name: String
var lifeStyle: LifeStyle

init(name: String, lifeStyle: LifeStyle) {
self.name = name
self.lifeStyle = lifeStyle
}

func beBetterFrom() -> [String] {
return lifeStyle.lifeStyle()
}

func perf(with addition: String) -> [String] {
var newStyle = lifeStyle.lifeStyle()
newStyle.append(addition)
return newStyle
}
}

struct Recommend: LifeStyle {
func lifeStyle() -> [String] {
return ["read", "travel"]
}
}

let p = Person(name: "p", lifeStyle: Recommend())
print(p.beBetterFrom())
/// ["read", "travel"]
let newStyle = p.perf(with: "love")
print(newStyle)
/// ["read", "travel", "love"]

在扩展里添加协议遵循

扩展可以补充协议中已存在的内容,或是提供默认的实现,还可以给协议新增内容。

有条件的遵循协议

1
2
3
extension Array where Element: Equatable  {
// code
}

使用扩展声明采纳协议

如果一个类型已经遵循类协议的要求,但是还没有声明采纳协议,可以通过一个空的扩展来显式地声明它采纳协议。

1
2
3
4
5
6
7
8
9
10
11
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}

let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)

协议类型的集合

1
2
3
4
let things: [TextRepresentable] = [game, d12, simonTheHamster]
for thing in things {
print(thing.textualDescription)
}

协议继承

1
2
3
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition goes here
}

类专用的协议

1
2
3
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// class-only protocol definition goes here
}

协议组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// Prints "Happy birthday, Malcolm, you're 21!"

协议遵循的检查

  • 如果实例遵循协议 is 运算符返回 true 否则返回 false
  • as? 版本的向下转换运算符返回协议的可选项,如果实例不遵循这个协议的话值就是 nil
  • as! 版本的向下转换运算符强制转换协议类型并且在失败是触发运行时错误

可选协议要求

协议扩展

泛型

类型形式参数

1
func swapTwoValues<T>(_ a: inout T, _ b: inout T)

占位符 T 就是类型形式参数,当我们调用函数时,用实际类型来替换类型形参。

## 泛型类型

Swift 允许自定义泛型类型,它们可以使自定义类、结构体、枚举。

1
2
3
4
5
6
7
8
9
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}

扩展一个泛型类型

1
2
3
4
5
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}

类型约束

类型约束语法

1
2
3
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}

类型约束的应用

1
2
3
4
5
6
7
8
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}

关联类型

定义一个协议时,声明一个或多个关联类型是很有用的。关联类型个协议中用到的类型一个占位符名称,直到采纳协议时,才指定实际类型。

关联类型的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protocol Container {
associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}

struct Stack<Element>: Container {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}

// 可省略,应为 Swift 可以推断出此处的关联类型
// typealias ItemType = Element

mutating func append(_ item: Element) {
self.push(item)
}

var count: Int { items.count }

subscript(i: Int) -> Element {
return items[i]
}
}

给关联类型添加约束

1
2
3
4
5
6
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}

在关联类型约束里使用协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}

extension Stack: SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var res = Stack()
for i in (count-size)..<count {
res.append(self[i])
}
return res
}
}

var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
print(stackOfInts.suffix(2))
// "Stack<Int>(items: [20, 30])"

扩展现有类型来指定关联类型

Swift 中的 Array 类型已经提供了 Container 协议中的方法和属性,我们只需要用一个空的扩展显示地声明采纳协议即可:

1
extension Array: Container {}

现在我们可以把任何 Array 当做一个 Container 使用。

泛型 Where 分句

1
2
3
4
5
6
7
8
9
10
func allItemsMatch<C1: Container, C2: Container>(
_ c1: C1, _ c2: C2) -> Bool where C1.Item == C2.Item {
if c1.count != c2.count { return false }
for i in 0..<c1.count {
if c1[i] != c2[i] {
return false
}
}
return true
}

带有泛型 Where 分句的扩展

1
2
3
4
5
6
7
8
9
extension Container where Item == Double {
func average() -> Double {
var sum = 0.0
for i in 0..<count {
sum += self[i]
}
return sum / Double(count)
}
}

泛型下标

1
2
3
4
5
6
7
8
9
10
11
extension Container {
// 传入的 indices 形参是一个整数的序列
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var res = [Item]()
for i in indices {
res.append(self[i])
}
return res
}
}

不透明类型

具有不透明返回类型的函数或者方法会隐藏它返回值的具体类型信息,而以它支持的协议进行描述。隐藏类型信息在模块之间调用代码时很好用,因为返回值的具体类型可以保持私有。不同于返回一个协议类型的值,不透明类型保持了类型的身份——编译器可以访问类型信息,但是模块不能。

不透明类型也可以理解为“反向泛型”,泛型受调用者约束,而不透明类型受被调用者约束。

函数不能返回带有 Selfassociatedtype 的协议,而不透明类型可以。函数可以返回不同的协议类型,不透明类型每次必须返回相同的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
protocol UIMode {
func color() -> UIColor
}

struct LightMode: UIMode {
func color() -> UIColor {
return .white
}
}

struct DarkMode: UIMode {
func color() -> UIColor {
return .black
}
}

let light = LightMode()
let dark = DarkMode()

func getMode(isDay: Bool) -> UIMode {
isDay ? light : dark
}

print(getMode(isDay: true).color())
print(getMode(isDay: false).color())

struct AutoMode: UIMode {
var isDay: Bool

func color() -> UIColor {
isDay ? .white : .black
}
}

func getModeOpaque(isDay: Bool) -> some UIMode {
return AutoMode(isDay: isDay)
}

print(getModeOpaque(isDay: true).color())
print(getModeOpaque(isDay: false).color())
您的支持将鼓励我继续创作!
0%