美文网首页
(WWDC) 初探 Combine

(WWDC) 初探 Combine

作者: FicowShen | 来源:发表于2019-06-19 14:18 被阅读0次

    内容概览

    • 前言
    • Combine
    • Publishers
    • Subscribers
    • Operators




    前言

    假设你需要构建如下应用:

    App要求:

    • 实时验证用户名是否有效
    • 匹配密码
    • 响应用户界面

    你会如何实现?实现的过程会应用到哪些概念?



    你需要使用 Target/Action 来获取输入框的最新值,使用计时器来实现定期检查,使用 KVO 来控制加载指示器的状态。

    你还需要使用 URLSession 来进行网络请求,汇总三个输入框的结果,然后通过 KVC 来控制按钮的可用状态。



    涉及到的异步接口:

    • Target/Action
    • 通知中心
    • URLSession
    • KVC
    • 定制的回调



    是否觉得,这些步骤略显繁琐?
    是否有更好的解决方案呢?




    Combine

    Combine 提供了统一的、声明式的API,可以随着时间的推移处理多个值



    Combine 特性:

    • 泛型
    • 类型安全
    • 组合优先
    • 由请求驱动



    核心概念:

    • Publishers
    • Subscribers
    • Operators




    Publisher

    • 定义值和错误如何产生
    • 值类型
    • 允许 Subscriber 的注册
    protocol Publisher {
    
        associatedtype Output
        associatedtype Failure: Error
        
        func subscribe<S: Subscriber>(_ subscriber: S)
          where S.Input == Output, S.Failure == Failure
    }
    

    为 Publisher 拓展通知中心

    extension NotificationCenter {
        struct Publisher: Combine.Publisher {
            typealias Output = Notification
            typealias Failure = Never
            init(center: NotificationCenter, name: Notification.Name, object: Any? = nil)
        }
    }
    




    Subscribers

    • 接收值和完成事件
    • 引用类型
    protocol Subscriber {
    
        associatedtype Input
        associatedtype Failure: Error
        
        func receive(subscription: Subscription)
        func receive(_ input: Input) -> Subscribers.Demand
        func receive(completion: Subscribers.Completion<Failure>)
    }
    
    extension Subscribers {
    
        class Assign<Root, Input>: Subscriber, Cancellable {
            typealias Failure = Never
        }
        
        init(object: Root, keyPath: ReferenceWritableKeyPath<Root, Input>)
    }
    



    Publisher 和 Subscriber 之间的关系

    1. Subscriber 被绑定到 Publisher 上
    2. Publisher 发送一个订阅
    3. Subscriber 向 Publisher 请求值
    4. Publisher 向 Subscriber 发送值
    5. Publisher 发送完成事件



    使用 Publisher 和 Subscriber 的示例代码:

    class Wizard {
        var grade: Int
    }
    
    let merlin = Wizard(grade: 5)
    
    let graduationPublisher = NotificationCenter.Publisher(center: .default, name: .graduated, object: merlin)
    let gradeSubscriber = Subscribers.Assign(object: merlin, keyPath: \.grade)
    
    graduationPublisher.subscribe(gradeSubscriber)
    

    以上代码由于类型不匹配,会遭遇如下错误:



    看来,我们需要通过某种方式来实现类型匹配。




    Operators

    • 输入 Publisher
    • 形容改变值的行为
    • 订阅 Publisher (向上游)
    • 发送结果给 Subscriber (向下游)
    • 值类型
    extension Publishers {
        struct Map<Upstream: Publisher, Output>: Publisher {
            typealias Failure = Upstream.Failure
            
            let upstream: Upstream
            let transform: (Upstream.Output) -> Output
        } 
    }
    



    现在,我们可以通过优雅的方式解决之前的问题。

    let graduationPublisher = NotificationCenter.Publisher(center: .default, name: .graduated, object: merlin)
    let gradeSubscriber = Subscribers.Assign(object: merlin, keyPath: \.grade)
    
    let converter = Publishers.Map(upstream: graduationPublisher) { note in
        return note.userInfo?["NewGrade"] as? Int ?? 0
    }
    
    converter.subscribe(gradeSubscriber)
    

    使用 Map Operator,我们完成了订阅操作。

    不过,还可以把步骤简化一下:

    extension Publisher {
        func map<T>(_ transform: @escaping (Output) -> T) -> Publishers.Map<Self, T> {
            return Publishers.Map(upstream: self, transform: transform)
        }
    }
    
    let cancellable = NotificationCenter.default.publisher(for: .graduated, object: merlin)
                        .map { note in
                            return note.userInfo?["NewGrade"] as? Int ?? 0
                        }
                        .assign(to: \.grade, on: merlin)
    

    声明式 Operator API 的优点:

    • 函数式转换
    • 链式操作
    • 错误处理
    • 线程和队列操作
    • 调度和时间



    在使用 Operator 时,优先尝试进行组合

    在对单个值(如:独立的网络请求)进行异步操作时,推荐使用 Future
    在对多个值(如:帐号密码输入框的事件流)进行异步操作时,推荐使用 Publisher



    使用 map 的部分其实还可以优化

    使用 compactMap 简化操作:

    进行筛选:

    截取部分值:



    还可以组合多个 Publisher:

    • Zip
      • 将多个输入的单次值组合后转换为单个元组
      • 是一个"when/and" 操作,所有输入都有输入值才能产生输出
      • 要求具备所有输入的输入值时,才能产生输入


    • CombineLatest
      • 将多个输入的单次值组合后转换为单个值
      • 是一个"when/or",其中一个输入有输入值就能产生输出
      • 只要有一个输入有输入值,就会执行输出
      • 会存储所有输入的最后一个输入值
    使用建议
    • 使用 filter处理通知中心的通知
    • 使用 zip 处理等待两个请求完成的事件
    • 使用 decode 处理 URLResponse 的 data

    更多内容

    • 错误处理和取消订阅
    • 调度和时间
    • 设计模式

    欢迎继续阅读 (WWDC) 实践 Combine




    参考内容:
    Introducing Combine




    转载请注明出处,谢谢~

    相关文章

      网友评论

          本文标题:(WWDC) 初探 Combine

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