前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于Combine的响应式UIControl

基于Combine的响应式UIControl

原创
作者头像
用户8438817
发布2023-04-21 16:38:01
9270
发布2023-04-21 16:38:01
举报
文章被收录于专栏:写写代码写写代码

一、概述

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

代码语言:javascript
复制
```
/// 自定义
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~~~")
        }
代码语言:javascript
复制
        /// 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
        }

    }
代码语言:javascript
复制
    /// 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 {
    
}
```

三、如何用?

代码语言:javascript
复制
```
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)
```

还可以再简化,只保留闭包即可,这部分封装就留着自由发挥了。

如下看起来清爽的多

代码语言:javascript
复制
```
btn_2.action(events: .touchUpInside) { [weak self] (btn) in
    guard let `self` = self else {
      return
    }
    print("btn combint click")
}
```

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
  • 二、如何实现?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档