一、概述
iOS开发中UIKit中控件的交互方式默认是Target-Action,这种方式简单且直观。不过,一个问题在于编码方式太过于繁琐,需要定义一个方法,然后调用addTartget方式进行绑定;在复杂页面交互,需要跨多级数据传递的时候,就变得异常繁琐。
后面响应式和函数式编程兴起,诞生RxSwift等的响应式框架,全新的开发体验确实提高的开发效率,不过带来的问题就是堆栈太深,排查问题不利于排查。也会有一定的损耗,这么多的堆栈必然占用更多的系统资源,性能的话会有一定影响。
iOS13后,apple要推广swiftUI带来了Combine,其实apple的响应式框架,亲儿子,在框架底层和Swift层面都进行一定的优化,堆栈和性能会比RxSwift等更优。随着iOS13的不断普及,Combine会越来越受欢迎。
不过SwiftUI发展必然不会那么快速,项目中还是有很多的UIKit的代码需要维护。
本文不在于介绍Combine的理论知识,而是在于扩展UIKit的UIControl支持响应式编程方式。
二、如何实现?
自定义 Publisher 和 Subscriber
- 第一步,自定义Subscription 中介对象
- 第二步,自定义Publisher 发布者
- 第三部,扩展第三方支持Publisher
/// 自定义
extension Publishers {
/// 1、自定义 Subscription
/// 定义输入类型为UIControl,错误类型为Never
private final class UIControlSubscription<S:Subscriber, Control:UIControl> : Subscription where S.Input == Control, S.Failure == Never {
private var subscriber: S?
private var control: Control
private var events: Control.Event
/// Step 1 : 初始化
init(subscriber: S?, control: Control, events: Control.Event) {
self.subscriber = subscriber
self.control = control
self.events = events
configControl()
}
deinit {
print("UIControlSubscription deinit~~~")
}
/// Step 2 : 关联 与 控制
func configControl() {
self.control.addTarget(self, action: #selector(eventHandler), for: self.events)
}
@objc func eventHandler() {
// 忽略返回值
_ = self.subscriber?.receive(self.control)
}
func request(_ demand: Subscribers.Demand) {
}
/// Step 3 : 销毁
func cancel() {
// 销毁订阅者
subscriber = nil
}
}
/// 2、自定义 Publisher
struct UIControlPublisher<Control: UIControl> : Publisher {
typealias Output = UIControl
typealias Failure = Never
private var control: Control
private var events: Control.Event
/// Step 1 : 初始化
init(control: Control, events: Control.Event) {
self.control = control
self.events = events
}
/// Step 2 :通过 Subscription 将 订阅者Subscriber 连接到 发布者Publisher
func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, UIControl == S.Input {
let subscription = UIControlSubscription(subscriber: subscriber, control: self.control, events: self.events)
subscriber.receive(subscription: subscription)
}
}
}
extension UIControl {
func publisher(events: UIControl.Event) -> Publishers.UIControlPublisher<UIControl> {
return Publishers.UIControlPublisher(control: self, events: events)
}
}
extension UISwitch {
func publisher() -> AnyPublisher<Bool, Never> {
return Publishers.UIControlPublisher(control: self, events: .valueChanged)
.map{ ($0 as! UISwitch).isOn }
.eraseToAnyPublisher()
}
}
extension UISlider {
func publisher() -> AnyPublisher<Float, Never> {
return Publishers.UIControlPublisher(control: self, events: .valueChanged)
.map{ ($0 as! UISlider).value }
.eraseToAnyPublisher()
}
}
extension UITextField {
}
三、如何用?
private var cancelList: Set<AnyCancellable> = []
let btn_2 = UIButton.init(type: .custom)
btn_2.setTitle("combine", for: .normal)
btn_2.backgroundColor = .blue
addSubview(btn_2)
btn_2.publisher(events: .touchUpInside)
.receive(on: RunLoop.main)
.sink { [weak self] (btn) in
guard let `self` = self else {
return
}
print("btn combint click")
}.store(in: &cancelList)
还可以再简化,只保留闭包即可,这部分封装就留着自由发挥了。
如下看起来清爽的多
btn_2.action(events: .touchUpInside) { [weak self] (btn) in
guard let `self` = self else {
return
}
print("btn combint click")
}
网友评论