美文网首页
基于Combine的响应式UIControl

基于Combine的响应式UIControl

作者: 双鱼子曰1987 | 来源:发表于2023-04-20 16:33 被阅读0次

    一、概述

    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")
    }
    

    相关文章

      网友评论

          本文标题:基于Combine的响应式UIControl

          本文链接:https://www.haomeiwen.com/subject/ddnrjdtx.html