由 Combine 实践引起的编程思想的改变
highlight: xcode theme: juejin
一个新事物的出现,首先要简单了解基本概念,由理论出发到实践检验,再经由实践得出理论
概念
Combine
Customize handling of asynchronous events by combining event-processing operators.
- 自定义处理异步事件
- 通过把事件组合处理
- 简单了解这些就够了,接下来看实践,再由实践得出更多的理论
实践
案例一
有这样两个页面
- 老师发布作业的页面,名称和班级是必填的,直接影响到底部发布按钮的点击状态
- 学生提交作业的页面,文字和图片必选其一,每个改动都会影响到底部提交按钮的状态
方案 一
正常情况下我们的实现逻辑是
- 一个 ViewController 持有两个 SubView,一个 ActionBtn,subView 的内容改变,回调通知(block、delegate、notification)到 ViewController,然后改变 ActionBtn 的状态
- 为了复用和解耦,我们会给 subView 添加一个逻辑处理类 (handler、viewModel、presenter)来处理 view 里面控件逻辑
- 最终点击按钮的时候,封装数据发送请求到服务
- 如图所示,model 层在这样的流程中毫无用武之地,同时回调太多也会导致代码繁琐和维护困难
- 究其原因在于,1. 异步,2 组合,
- 假如只有一个 view,使用异步回调勉强还可以接受
- ~~如果都放在 VC 里面同步处理,远古写法就算了~~
方案二
使用 Combine
- 一个 ViewController 持有两个 SubView,一个 ActionBtn,一个Model,把 Model 传递到 SubView 中,所有 SubView 的内容更改,都是对 Model 的属性的更改
- ViewController 监听 Model 属性的改变,同时改变 ActionBtn 的状态,或者直接把 Btn 的状态绑定到属性上
- 点击按钮,把 Model 直接传递进去进行网络请求
-
明面上我们只是添加了 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)
} } ``` -
基于系统的 Combine 库,我们可以用更少、更简洁的代码做到异步处理和组合处理,这也是 Combine 的意义所在
案例二
照例先看一个页面需求
-
根据选中个数,顶部导航栏展示全选和取消全选,底部按钮展示是否可点击和数字
这次我们直接使用 Combine 的方案,
- 一个 ViewController 持有 TableView,NaviView,BottomView
- 一个统一的数据源 DataModel,由 Model 层处理所有的数据逻辑
-
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
} ```
小结
-
上面的两个例子,好像跟我们平时的代码逻辑有些变化,又好像没有变化
- 他们本质上到底有哪些不同?
理论
The Combine framework provides a declarative Swift API for processing values over time
- Combine 提供了一套声明式的 Swift API,用来处理随时间变化的 Values
- Combine 是苹果官方的一个 Swift 响应式编程 框架,是一种面向数据流和变化传播的 声明式编程范式
- 而响应式编程,比较熟悉的有 RxSwift,ReactiveCocoa等,Combine 跟他们一样,都用了大量的函数式 编程的写法
函数式编程:
- Swift 的高阶函数,reduce,map 等都属于函数式编程
- 简单说就是把运算过程尽可能写成一系列【嵌套】的函数调用,而不是一行行的方法执行
- 而要实现这种方式就要尽可能的使用【表达式】,而不是【执行语句】
- 举个🌰:
swift // 数字1、2 的和是3 sum(1, 2) = 3 // 数字 1 加 2 等于3 1 + 2 = 3
声明式编程:
-
简单理解下面两句话: ```
-
View 上新增一个 Label,它的字体大小是 16,文字是 "label",字体颜色是黑色(命令/指令式语句)
-
View 上新增一个字体大小是 16,文字是 "label",字体颜色是黑色的 Label(描述性语句) ```
-
-
再看开发过程中最常用的 TableViewCell,直接设置属性的方式在逐渐的被声明式的属性替换
- 指令/命令式编程更多的告诉计算机怎么做,How to do,面向的是过程,一步步执行
- 声明式编程更多的是做什么,What to do,要的是一个结果
小结
- 使用 Combine 不仅仅使得代码清晰,逻辑简洁,方便维护,更多的是编程思想的一个碰撞和改变,学习响应式编程,这是一个非常好的切入点。
- 尤其 Combine 是内置在 Apple 系统的,对 App包体积毫无影响,对比 RxSwift 的性能也更好。
- 随着系统更新,我们不可避免的要接触和学习 SwiftUI,Combine 更是必不可少。
- 一家之谈,仅抛砖引玉,欢迎交流。