由 Combine 实践引起的编程思想的改变

语言: CN / TW / HK

highlight: xcode theme: juejin


一个新事物的出现,首先要简单了解基本概念,由理论出发到实践检验,再经由实践得出理论

概念

Combine

Customize handling of asynchronous events by combining event-processing operators.

  1. 自定义处理异步事件
  2. 通过把事件组合处理
  3. 简单了解这些就够了,接下来看实践,再由实践得出更多的理论

实践

案例一

有这样两个页面

  1. 老师发布作业的页面,名称和班级是必填的,直接影响到底部发布按钮的点击状态
  2. 学生提交作业的页面,文字和图片必选其一,每个改动都会影响到底部提交按钮的状态

方案 一

正常情况下我们的实现逻辑是

  1. 一个 ViewController 持有两个 SubView,一个 ActionBtn,subView 的内容改变,回调通知(block、delegate、notification)到 ViewController,然后改变 ActionBtn 的状态
  2. 为了复用和解耦,我们会给 subView 添加一个逻辑处理类 (handler、viewModel、presenter)来处理 view 里面控件逻辑
  3. 最终点击按钮的时候,封装数据发送请求到服务
  4. 如图所示,model 层在这样的流程中毫无用武之地,同时回调太多也会导致代码繁琐和维护困难
  5. 究其原因在于,1. 异步,2 组合,
  6. 假如只有一个 view,使用异步回调勉强还可以接受
  7. ~~如果都放在 VC 里面同步处理,远古写法就算了~~

方案二

使用 Combine

  1. 一个 ViewController 持有两个 SubView,一个 ActionBtn,一个Model,把 Model 传递到 SubView 中,所有 SubView 的内容更改,都是对 Model 的属性的更改
  2. ViewController 监听 Model 属性的改变,同时改变 ActionBtn 的状态,或者直接把 Btn 的状态绑定到属性上
  3. 点击按钮,把 Model 直接传递进去进行网络请求
  4. 明面上我们只是添加了 Model 层,使用监听来代替回调,但实际上他们有本质的不同,请看代码: ```swift /// 发布、上传 作业对应的 Model public class CreateModel { /// 标题 @Published public var title: String = "" /// 班级 ID @Published public var classID: String = "" /// 是否可以发布 public var validCreateTask: AnyPublisher { return Publishers.CombineLatest($title, $classID) .map { (title, classID) in return !title.isEmpty && !classID.isEmpty }.eraseToAnyPublisher() }
    }

    class CreateTaskViewController: UIViewController { override func viewDidLoad() { workModel.validCreateTask .assign(to: .isEnabled, on: self.actionBtn) .store(in: &cancellSet)
    } } ```

  5. 基于系统的 Combine 库,我们可以用更少、更简洁的代码做到异步处理和组合处理,这也是 Combine 的意义所在

案例二

照例先看一个页面需求

  1. 根据选中个数,顶部导航栏展示全选和取消全选,底部按钮展示是否可点击和数字

这次我们直接使用 Combine 的方案,

  1. 一个 ViewController 持有 TableView,NaviView,BottomView
  2. 一个统一的数据源 DataModel,由 Model 层处理所有的数据逻辑
  3. ViewController 和 SubViews 根据数据变化进行界面展示 ```swift class DataModel {

    var totalList: [ItemModel] = []
    @Published var isEditStatus: Bool = false
    @Published var selectedList: [ItemModel] = []
    

    } extension DataModel {

    /// 数据变化
    var dataChanged: AnyPublisher<([ItemModel], Bool), Never> {
        return Publishers.CombineLatest($selectedList, $isEditStatus)
            .eraseToAnyPublisher()
    }
    /// 是否全选
    var isFullSelected: Bool {
        return selectedList.count >= totalList.count
    }
    // add
    // remove
    // contain
    // selecteAll
    // unSelecteAll
    

    } ```

    小结

  4. 上面的两个例子,好像跟我们平时的代码逻辑有些变化,又好像没有变化

  5. 他们本质上到底有哪些不同?

理论

The Combine framework provides a declarative Swift API for processing values over time

  1. Combine 提供了一套声明式的 Swift API,用来处理随时间变化的 Values
  2. Combine 是苹果官方的一个 Swift 响应式编程 框架,是一种面向数据流和变化传播的 声明式编程范式
  3. 而响应式编程,比较熟悉的有 RxSwift,ReactiveCocoa等,Combine 跟他们一样,都用了大量的函数式 编程的写法

函数式编程:

  1. Swift 的高阶函数,reduce,map 等都属于函数式编程
  2. 简单说就是把运算过程尽可能写成一系列【嵌套】的函数调用,而不是一行行的方法执行
  3. 而要实现这种方式就要尽可能的使用【表达式】,而不是【执行语句】
  4. 举个🌰: swift // 数字1、2 的和是3 sum(1, 2) = 3 // 数字 1 加 2 等于3 1 + 2 = 3

声明式编程:

  1. 简单理解下面两句话: ```

    1. View 上新增一个 Label,它的字体大小是 16,文字是 "label",字体颜色是黑色(命令/指令式语句)

    2. View 上新增一个字体大小是 16,文字是 "label",字体颜色是黑色的 Label(描述性语句) ```

  2. 再看开发过程中最常用的 TableViewCell,直接设置属性的方式在逐渐的被声明式的属性替换

  3. image.pngimage.png
  4. 指令/命令式编程更多的告诉计算机怎么做,How to do,面向的是过程,一步步执行
  5. 声明式编程更多的是做什么,What to do,要的是一个结果

小结

  1. 使用 Combine 不仅仅使得代码清晰,逻辑简洁,方便维护,更多的是编程思想的一个碰撞和改变,学习响应式编程,这是一个非常好的切入点。
  2. 尤其 Combine 是内置在 Apple 系统的,对 App包体积毫无影响,对比 RxSwift 的性能也更好。
  3. 随着系统更新,我们不可避免的要接触和学习 SwiftUI,Combine 更是必不可少。
  4. 一家之谈,仅抛砖引玉,欢迎交流。

参考资料

  1. Apple Developer Documentation Combine
  2. Apple 官方异步编程框架:Swift Combine 简介 - 小专栏
  3. Apple 官方异步编程框架:Swift Combine 应用 - 小专栏
  4. 从响应式编程到 Combine 实践
  5. Swift: Combine Basics & Intro (2022, Xcode 12, Swift 5) - iOS Development
  6. 函数式编程初探