美文网首页
ReactiveCocoa 5.0 的 简单 应用

ReactiveCocoa 5.0 的 简单 应用

作者: d4d98020ef88 | 来源:发表于2016-11-25 15:01 被阅读603次

    写在前面的话

    之前只是用过ReactiveCocoa 2.5 的OC版本,简单的理解了一些Signal和Signal的基本操作符。Swift3.0 出来之后粗略的看了几个星期,几个星期看下来感觉跟JS 还是很像的,但是又有很多OC的影子,总的来说Swift比OC更加友好,更加的好用。

    由于ReactiveCocoa 5.0刚出来网上基本上没有详细的教程,所以只能硬着头皮看ReactiveCocoa的英文文档了,然后看文档的过程中发现了@没故事的卓同学的翻译,基本上ReactiveCocoa 4.0的文档他都翻译出来了。对比5.0 文档中的修改,还是比较容易理解的。但是有很多细节的地方翻译是有问题的,总的来说还是看原文的文档比较好。

    ReactiveCocoa 4 文档翻译目录

    ReactiveCocoa 的基础知识

    这边主要是介绍ReactiveCocoa 5.0 的框架部分,其他的Signal操作符其实跟OC的区别不是很大。
    原文地址:
    https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md

    Events(事件)

    事件是ReactiveCocoa 中传播(center-piece of communication)的核心。Event 是一个枚举类型,有四个类型。每一种情况都会发送给Signal 的订阅者。

    /// Represents a signal event.
    ///
    /// Signals must conform to the grammar:
    /// `value* (failed | completed | interrupted)?`
    public enum Event<Value, Error: Swift.Error> {
        /// A value provided by the signal.
        case value(Value)
    
        /// The signal terminated because of an error. No further events will be
        /// received.
        case failed(Error)
    
        /// The signal successfully terminated. No further events will be received.
        case completed
    
        /// Event production on the signal has been interrupted. No further events
        /// will be received.
        ///
        /// - important: This event does not signify the successful or failed
        ///              completion of the signal.
        case interrupted
    
        /// Whether this event indicates signal termination (i.e., that no further
        /// events will be received).
        public var isTerminating: Bool {
            switch self {
            case .value:
                return false
    
            case .failed, .completed, .interrupted:
                return true
            }
        }
    
        /// Lift the given closure over the event's value.
        ///
        /// - important: The closure is called only on `value` type events.
        ///
        /// - parameters:
        ///   - f: A closure that accepts a value and returns a new value
        ///
        /// - returns: An event with function applied to a value in case `self` is a
        ///            `value` type of event.
        public func map<U>(_ f: (Value) -> U) -> Event<U, Error> {
            switch self {
            case let .value(value):
                return .value(f(value))
    
            case let .failed(error):
                return .failed(error)
    
            case .completed:
                return .completed
    
            case .interrupted:
                return .interrupted
            }
        }
    
        /// Lift the given closure over the event's error.
        ///
        /// - important: The closure is called only on failed type event.
        ///
        /// - parameters:
        ///   - f: A closure that accepts an error object and returns
        ///        a new error object
        ///
        /// - returns: An event with function applied to an error object in case
        ///            `self` is a `.Failed` type of event.
        public func mapError<F>(_ f: (Error) -> F) -> Event<Value, F> {
            switch self {
            case let .value(value):
                return .value(value)
    
            case let .failed(error):
                return .failed(f(error))
    
            case .completed:
                return .completed
    
            case .interrupted:
                return .interrupted
            }
        }
    
        /// Unwrap the contained `value` value.
        public var value: Value? {
            if case let .value(value) = self {
                return value
            } else {
                return nil
            }
        }
    
        /// Unwrap the contained `Error` value.
        public var error: Error? {
            if case let .failed(error) = self {
                return error
            } else {
                return nil
            }
        }
    }
    

    上面是ReactiveCocoa 的源码,可以清楚的看到Event的结构跟属性。这里需要强调的是failed(Error),completed,interrupted三种类型出现都会取消Signal的订阅,这就是Signal的 dispose方法,这个比较关键。map 和 mapError 方法则是转换和转换错误,这里就不介绍了。

    Observers (观察者)

    Observer是指任何等待从信号中接收事件的东西。类似于OC中的订阅者,这边翻译为订阅者应该更为贴切,我们还是先看看ReactiveCocoa 的源码。

    /// A protocol for type-constrained extensions of `Observer`.
    public protocol ObserverProtocol {
        associatedtype Value
        associatedtype Error: Swift.Error
    
        /// Puts a `value` event into `self`.
        func send(value: Value)
    
        /// Puts a failed event into `self`.
        func send(error: Error)
    
        /// Puts a `completed` event into `self`.
        func sendCompleted()
    
        /// Puts an `interrupted` event into `self`.
        func sendInterrupted()
    }
    
    /// An Observer is a simple wrapper around a function which can receive Events
    /// (typically from a Signal).
    public final class Observer<Value, Error: Swift.Error> {
        public typealias Action = (Event<Value, Error>) -> Void
    
        /// An action that will be performed upon arrival of the event.
        public let action: Action
    
        /// An initializer that accepts a closure accepting an event for the 
        /// observer.
        ///
        /// - parameters:
        ///   - action: A closure to lift over received event.
        public init(_ action: @escaping Action) {
            self.action = action
        }
    }
    

    订阅者可以观察Event这个 枚举属性,也可以单独的对某一个状态进行订阅。

    Property (属性)

    Property 一个属性表现为 PropertyType协议(protocol), 保存一个值,并且会将将来每次值的变化通知给观察者们。

    /// Represents a property that allows observation of its changes.
    ///
    /// Only classes can conform to this protocol, because having a signal
    /// for changes over time implies the origin must have a unique identity.
    public protocol PropertyProtocol: class {
        associatedtype Value
    
        /// The current value of the property.
        var value: Value { get }
    
        /// The values producer of the property.
        ///
        /// It produces a signal that sends the property's current value,
        /// followed by all changes over time. It completes when the property
        /// has deinitialized, or has no further change.
        var producer: SignalProducer<Value, NoError> { get }
    
        /// A signal that will send the property's changes over time. It
        /// completes when the property has deinitialized, or has no further
        /// change.
        var signal: Signal<Value, NoError> { get }
    }
    

    property的当前值可以通过获取 value获得。producer返回一个会一直发送值变化信号生成者(signal producer ),

    <~运算符是提供了几种不同的绑定属性的方式。注意这里绑定的属性必须是 MutablePropertyType类型的。

    property <~ signal将一个属性和信号绑定在一起,属性的值会根据信号送过来的值刷新。
    property <~ producer 会启动这个producer,并且属性的值也会随着这个产生的信号送过来的值刷新。
    property <~ otherProperty将一个属性和另一个属性绑定在一起,这样这个属性的值会随着源属性的值变化而变化。`

    文档的翻译是这样的,具体的用法下面会通过demo来介绍。

    Actions (动作)

    动作用 Action类型表示,指当有输入时会做一些工作。当动作执行时,会有0个或者多个值输出;或者会产生一个失败。

    Action用来处理用户交互时做一些处理很方便,比如当一个按钮点击时这种动作。Action也可以和一个属性自动关联disabled。比如当一个UI控件的关联Action被设置成disabled时,这个控件也会disabled。

    为了和NSControl和UIControl交互,RAC提供了 CocoaAction类型可以桥接到OC下使用。

    ** 其他的一些内容跟OC版本差不多,具体的还是要看API。**

    ReactiveCocoa 的使用

    上面的介绍不是很清晰,我现在也是在学习阶段。

            let label = UILabel.init()
            label.textAlignment = .center
            self.view.addSubview(label)
            label.snp.makeConstraints { (make) in
                make.center.equalToSuperview()
                make.width.height.equalTo(100)
            }
            
            let title: String = "2333"
            label.text = title
            
            let textField = UITextField.init()
            textField.borderStyle = .roundedRect
            self.view.addSubview(textField)
            textField.snp.makeConstraints { (make) in
                make.centerX.equalTo(label)
                make.top.equalTo(100)
                make.width.equalTo(200)
            }
    

    先创建一个label 和一个 textfield。

    //property <~ signal 将一个属性和信号绑定在一起,属性的值会根据信号送过来的值刷新。
    //property <~ producer 会启动这个producer,并且属性的值也会随着这个产生的信号送过来的值刷新。
    //property <~ otherProperty将一个属性和另一个属性绑定在一起,这样这个属性的值会随着源属性的值变化而变化。
    //DynamicProperty 类型用于桥接OC的要求KVC或者KVO的API,比如 NSOperation。要提醒的是大部分AppKit和UIKit的属性都不支持KVO,所以要观察它们值的变化需要通过其他的机制。相比 DynamicProperty要优先使用  MutablePropertyType类型。
    label.reactive.text <~ textField.reactive.continuousTextValues
    
    

    可以通过Signal将 label的text 跟 textfield的输入内容绑定。

    下面我们再看有一个实时搜索功能的demo
    /// 下面的demo可以通过RAC来实现 textField的实时搜索功能

            let textFieldStrings = textField.reactive.continuousTextValues
            let searchResults = textFieldStrings
                    .flatMap(.latest) { (query: String?) -> SignalProducer<(Data, URLResponse), NSError> in
                    let request = self.makeSearchRequest(escapedQuery: query)
                    return URLSession.shared.reactive
                        .data(with: request)
                        .retry(upTo: 2)
                        .flatMapError({ (error) in
                        print("Network error occurred: \(error)")
                        return SignalProducer.empty
                    })
            })
    

    根据textField的 输入内容进行网络请求

            let textFieldStrings = textField.reactive.continuousTextValues
            let searchResults = textFieldStrings
                .flatMap(.latest) { (query: String?) -> SignalProducer<(Data, URLResponse), NSError> in
                    let request = self.makeSearchRequest(escapedQuery: query)
                    return URLSession.shared.reactive
                        .data(with: request)
                        .retry(upTo: 2)
                        .flatMapError({ (error) in
                        print("Network error occurred: \(error)")
                        return SignalProducer.empty
                    })
            }
            .map { (data, response) -> [SearchResult] in
                let string = String.init(data: data, encoding: .utf8)
                //将data解析为json数据
                do {
                    let dic = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! Dictionary<String, Any>
                    debugPrint(dic)
                    let arr = dic["data"]
                    debugPrint(arr as! [Any])
                }catch {
                    debugPrint(error)
                }
                return [SearchResult.init(string: string)]
            }
            .throttle(1.5, on: QueueScheduler.main)
            .take(until: self.reactive.trigger(for: #selector(viewDidDisappear(_:))))
    

    使用map(转换)、throttle(缓冲) 对信号进行操作。

    searchResults.observe { event in
        //event 是一个枚举类型
        switch event {
        case let .value(values):
            debugPrint("Search results: \(values.first?.string)")
        case let .failed(error):
            print("Search error: \(error)")
        case .completed, .interrupted:
            debugPrint("search completed!!!")
            break
        }
    }
    

    对信号 进行订阅,就可以得到网络请求得到的数据,可以用于进行后续操作。

    最后的话

    总的来说ReactiveCocoa的学习难度还是很大的,当初OC也是花了将近一个月才慢慢理解ReactiveCocoa的用法。
    大家共勉吧,后续可能也会有一些学习的记录。这边文章只是抛砖引玉的记录了一些文档中的用法。

    相关文章

      网友评论

          本文标题:ReactiveCocoa 5.0 的 简单 应用

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