• SwiftUI 2.0 课程笔记 Chapter 6


    课程链接:https://www.bilibili.com/video/BV1q64y1d7x5?p=6

    课程项目仓库:https://github.com/cnatom/MemorizeSwiftUI

    协议protocol

    协议类似于其他语言中的接口interface,包含一些未实现的函数与变量(vars)。

    基本使用

    定义协议

    协议内的函数与变量不需要提供具体实现。

    protocol Moveable{
        func move(by: Int)
        var hasMoved: Bool { get }
        var distanceFromStart: Int { get set }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    类继承

    class或者struct可以使用:后加协议名的方式声明自己需要实现的协议。

    协议内的每一个变量与函数都需要被实现。

    struct/class PortableThing: Moveable{
        // 此处必须实现Moveable协议内所有函数与变量
    }
    
    • 1
    • 2
    • 3
    协议“继承”

    协议也可以像class和struct一样,用:加另一个协议名的方式来“继承”其他协议。

    如下,协议Vehicle继承了协议Moveable,然后类Car继承了协议Vehicle。此处Car类不仅需要实现Vehicle中的变量函数,还需要实现Moveable中的变量和函数。

    protocol Moveable{
        func move(by: Int)
        var hasMoved: Bool { get }
        var distanceFromStart: Int { get set }
    }
    protocol Vehicle: Moveable{
        var passengerCount: Int { get set }
    }
    class Car: Vehicle {
        // 此处必须实现Moveable与Vehicle协议内所有函数与变量
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    多继承

    一个class/struct也可以声明多个协议,并且需要提供每一个协议的实现。

    class Car: Vehicle, Impoundable, Leasable {
        // 需实现 Vehicle, Impoundable, Leasable 所有的函数与变量
    }
    
    • 1
    • 2
    • 3

    作用

    指定行为

    通常,我们通过结构、类或枚举来指定struct/class的行为。

    例如,class Car: Vehicle {},则指定了车辆(Car)要像交通工具(Vehicle)一样,可能具有“刹车”“加速”等行为(函数),也可能具有“车载量”“轮子数量”等参数(变量)。

    在课程项目中,EmojiMemoryGameView结构体通过实现View协议中的var body,来进行绘制页面这个行为。EmojiMemoryGame类继承了ObservableObjecta协议,使其具有value观测功能。

    struct EmojiMemoryGameView: View {...}  // 
    class EmojiMemoryGame: ObservableObjecta{...}
    
    • 1
    • 2
    泛型

    可以为泛型指定协议。如下,CardContent泛型指定了Equatable协议,使得CardContent之间可以被比较。

    struct MemoryGame<CardContent> where CardContent: Equatable {...}
    
    • 1
    限制extension

    在课程项目中,我们通过extensionArray添加了oneAndOnly的计算变量,任何一个Array都可以调用oneAndOnly

    extension Array {
        /// 返回Array中独一无二的变量
        var oneAndOnly: Element? { // 该计算变量的类型为泛型
            if self.count == 1 {
                return self.first
            } else {
                return nil
            }
        }
    }
    // 使用
    var a: Array<Int> = [1]
    print(a.oneAndArray) // output:1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    但如果我们限制一下extension的功能,只有Array中的元素是“可哈希”的,该Array才可以调用oneAndOnly

    可以通过以下方式来实现。

    // 此处Element继承了Hashable协议
    extension Array where Element: Hashable{
          /// 返回Array中独一无二的变量
        var oneAndOnly: Element? { // 该计算变量的类型为泛型
            if self.count == 1 {
                return self.first
            } else {
                return nil
            }
        }
    }
    // 使用
    var a: Array<Int> = [1]
    print(a.oneAndArray) // output:1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    因为元素类型Int继承了Hashable协议,因此数组a可以调用oneAndArray计算变量。

    限制函数参数类型

    我们也可以使用协议去限制指定函数的参数类型。

    init(data: Data) where Data: Collection, Data.Element: Identifiable
    
    • 1

    如上,指明了data需要继承Collection,data中的Element需要继承Identifiable。

    代码共享

    代码共享是协议最重要的功能之一。

    可以通过为协议创建extension来将具体实现添加到协议中,所有继承协议的子类都共享这些具体实现。

    View协议中就有大量已实现的函数,如我们常用的.foregroundColor以及其他所有的修饰器,都是通过extension View { ... }的方式来实现的。

    protocol View {
      	var body: some View
    }
    
    extension View{
      	func foregroundColor(_ color: Color) -> some View {...}
      	func font(_ font: Font?) -> some View {...}
      	func blur(radius: CGFloat, opaque: Bool) -> some View {...}
      	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Array协议中的filterfirstIndex(where:),同样是通过这种方式来实现的。

    extension还可以为协议中的 func 或 var 添加默认实现。

    这就是为什么我们明明没有实现 Observableobjects中的 objectwillchange,却依然可以调用objectwillchange

    总之,为协议添加extension是 Swift 中面向协议编程的关键。

    约束与收获

    通过协议,约束了某个class/struct的行为,我们不得不一一实现协议中的函数与变量,可能会需要额外的时间来满足这种约束。但我们收获的是自下而上的规范和统一的接口,这会使项目的迭代变得更加快速,功能扩展更加容易,实现高内聚、低耦合的优美代码。

    ……未完待续

  • 相关阅读:
    Docker(一) ----初始Docker
    C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)
    wifi6 feature 详解
    黄金投资面对K线图有哪些好用的交易策略?
    基于 java+springboot+vue 的酒店⺠宿⽹站250910
    南大通用数据库-Gbase-8a-学习-05-通过审计日志抓取Sql、Trace日志查看执行计划
    ​LeetCode解法汇总1465. 切割后面积最大的蛋糕
    【计算机网络】数据链路层
    大数据-玩转数据-Flink定时器
    vue2 按钮权限控制组件 Authority
  • 原文地址:https://blog.csdn.net/qq_15989473/article/details/125630573