ReactiveCocoa 5.0 的 簡(jiǎn)單 應(yīng)用

寫在前面的話

之前只是用過ReactiveCocoa 2.5 的OC版本,簡(jiǎn)單的理解了一些Signal和Signal的基本操作符。Swift3.0 出來之后粗略的看了幾個(gè)星期,幾個(gè)星期看下來感覺跟JS 還是很像的,但是又有很多OC的影子,總的來說Swift比OC更加友好,更加的好用。

由于ReactiveCocoa 5.0剛出來網(wǎng)上基本上沒有詳細(xì)的教程,所以只能硬著頭皮看ReactiveCocoa的英文文檔了,然后看文檔的過程中發(fā)現(xiàn)了@沒故事的卓同學(xué)的翻譯,基本上ReactiveCocoa 4.0的文檔他都翻譯出來了。對(duì)比5.0 文檔中的修改,還是比較容易理解的。但是有很多細(xì)節(jié)的地方翻譯是有問題的,總的來說還是看原文的文檔比較好。

ReactiveCocoa 4 文檔翻譯目錄

ReactiveCocoa 的基礎(chǔ)知識(shí)

這邊主要是介紹ReactiveCocoa 5.0 的框架部分,其他的Signal操作符其實(shí)跟OC的區(qū)別不是很大。
原文地址:
https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md

Events(事件)

事件是ReactiveCocoa 中傳播(center-piece of communication)的核心。Event 是一個(gè)枚舉類型,有四個(gè)類型。每一種情況都會(huì)發(fā)送給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的結(jié)構(gòu)跟屬性。這里需要強(qiáng)調(diào)的是failed(Error),completed,interrupted三種類型出現(xiàn)都會(huì)取消Signal的訂閱,這就是Signal的 dispose方法,這個(gè)比較關(guān)鍵。map 和 mapError 方法則是轉(zhuǎn)換和轉(zhuǎn)換錯(cuò)誤,這里就不介紹了。

Observers (觀察者)

Observer是指任何等待從信號(hào)中接收事件的東西。類似于OC中的訂閱者,這邊翻譯為訂閱者應(yīng)該更為貼切,我們還是先看看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這個(gè) 枚舉屬性,也可以單獨(dú)的對(duì)某一個(gè)狀態(tài)進(jìn)行訂閱。

Property (屬性)

Property 一個(gè)屬性表現(xiàn)為 PropertyType協(xié)議(protocol), 保存一個(gè)值,并且會(huì)將將來每次值的變化通知給觀察者們。

/// 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的當(dāng)前值可以通過獲取 value獲得。producer返回一個(gè)會(huì)一直發(fā)送值變化信號(hào)生成者(signal producer ),

<~運(yùn)算符是提供了幾種不同的綁定屬性的方式。注意這里綁定的屬性必須是 MutablePropertyType類型的。

property <~ signal將一個(gè)屬性和信號(hào)綁定在一起,屬性的值會(huì)根據(jù)信號(hào)送過來的值刷新。
property <~ producer 會(huì)啟動(dòng)這個(gè)producer,并且屬性的值也會(huì)隨著這個(gè)產(chǎn)生的信號(hào)送過來的值刷新。
property <~ otherProperty將一個(gè)屬性和另一個(gè)屬性綁定在一起,這樣這個(gè)屬性的值會(huì)隨著源屬性的值變化而變化。`

文檔的翻譯是這樣的,具體的用法下面會(huì)通過demo來介紹。

Actions (動(dòng)作)

動(dòng)作用 Action類型表示,指當(dāng)有輸入時(shí)會(huì)做一些工作。當(dāng)動(dòng)作執(zhí)行時(shí),會(huì)有0個(gè)或者多個(gè)值輸出;或者會(huì)產(chǎn)生一個(gè)失敗。

Action用來處理用戶交互時(shí)做一些處理很方便,比如當(dāng)一個(gè)按鈕點(diǎn)擊時(shí)這種動(dòng)作。Action也可以和一個(gè)屬性自動(dòng)關(guān)聯(lián)disabled。比如當(dāng)一個(gè)UI控件的關(guān)聯(lián)Action被設(shè)置成disabled時(shí),這個(gè)控件也會(huì)disabled。

為了和NSControl和UIControl交互,RAC提供了 CocoaAction類型可以橋接到OC下使用。

** 其他的一些內(nèi)容跟OC版本差不多,具體的還是要看API。**

ReactiveCocoa 的使用

上面的介紹不是很清晰,我現(xiàn)在也是在學(xué)習(xí)階段。

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

先創(chuàng)建一個(gè)label 和一個(gè) textfield。

//property <~ signal 將一個(gè)屬性和信號(hào)綁定在一起,屬性的值會(huì)根據(jù)信號(hào)送過來的值刷新。
//property <~ producer 會(huì)啟動(dòng)這個(gè)producer,并且屬性的值也會(huì)隨著這個(gè)產(chǎn)生的信號(hào)送過來的值刷新。
//property <~ otherProperty將一個(gè)屬性和另一個(gè)屬性綁定在一起,這樣這個(gè)屬性的值會(huì)隨著源屬性的值變化而變化。
//DynamicProperty 類型用于橋接OC的要求KVC或者KVO的API,比如 NSOperation。要提醒的是大部分AppKit和UIKit的屬性都不支持KVO,所以要觀察它們值的變化需要通過其他的機(jī)制。相比 DynamicProperty要優(yōu)先使用  MutablePropertyType類型。
label.reactive.text <~ textField.reactive.continuousTextValues

可以通過Signal將 label的text 跟 textfield的輸入內(nèi)容綁定。

下面我們?cè)倏从幸粋€(gè)實(shí)時(shí)搜索功能的demo
/// 下面的demo可以通過RAC來實(shí)現(xiàn) textField的實(shí)時(shí)搜索功能

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

根據(jù)textField的 輸入內(nèi)容進(jìn)行網(wǎng)絡(luò)請(qǐng)求

        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數(shù)據(jù)
            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(轉(zhuǎn)換)、throttle(緩沖) 對(duì)信號(hào)進(jìn)行操作。

searchResults.observe { event in
    //event 是一個(gè)枚舉類型
    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
    }
}

對(duì)信號(hào) 進(jìn)行訂閱,就可以得到網(wǎng)絡(luò)請(qǐng)求得到的數(shù)據(jù),可以用于進(jìn)行后續(xù)操作。

最后的話

總的來說ReactiveCocoa的學(xué)習(xí)難度還是很大的,當(dāng)初OC也是花了將近一個(gè)月才慢慢理解ReactiveCocoa的用法。
大家共勉吧,后續(xù)可能也會(huì)有一些學(xué)習(xí)的記錄。這邊文章只是拋磚引玉的記錄了一些文檔中的用法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

  • RAC使用測(cè)試Demo下載:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees閱讀 6,431評(píng)論 3 10
  • 1.ReactiveCocoa簡(jiǎn)介 ReactiveCocoa(簡(jiǎn)稱為RAC),是由Github開源的一個(gè)應(yīng)用于i...
    清蘂翅膀的技術(shù)閱讀 1,999評(píng)論 0 1
  • 1.ReactiveCocoa簡(jiǎn)介 ReactiveCocoa(簡(jiǎn)稱為RAC),是由Github開源的一個(gè)應(yīng)用于i...
    愛睡覺的魚閱讀 1,154評(píng)論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,785評(píng)論 18 139
  • 1.ReactiveCocoa常見操作方法介紹。 1.1 ReactiveCocoa操作須知 所有的信號(hào)(RACS...
    萌芽的冬天閱讀 1,034評(píng)論 0 5