寫在前面的話
之前只是用過ReactiveCocoa 2.5 的OC版本撬呢,簡單的理解了一些Signal和Signal的基本操作符。Swift3.0 出來之后粗略的看了幾個星期根暑,幾個星期看下來感覺跟JS 還是很像的,但是又有很多OC的影子徙邻,總的來說Swift比OC更加友好排嫌,更加的好用。
由于ReactiveCocoa 5.0剛出來網(wǎng)上基本上沒有詳細(xì)的教程缰犁,所以只能硬著頭皮看ReactiveCocoa的英文文檔了淳地,然后看文檔的過程中發(fā)現(xiàn)了@沒故事的卓同學(xué)的翻譯怖糊,基本上ReactiveCocoa 4.0的文檔他都翻譯出來了。對比5.0 文檔中的修改颇象,還是比較容易理解的蓬抄。但是有很多細(xì)節(jié)的地方翻譯是有問題的,總的來說還是看原文的文檔比較好夯到。
ReactiveCocoa 的基礎(chǔ)知識
這邊主要是介紹ReactiveCocoa 5.0 的框架部分嚷缭,其他的Signal操作符其實跟OC的區(qū)別不是很大。
原文地址:
https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/FrameworkOverview.md
Events(事件)
事件是ReactiveCocoa 中傳播(center-piece of communication)的核心耍贾。Event 是一個枚舉類型阅爽,有四個類型。每一種情況都會發(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)跟屬性。這里需要強調(diào)的是failed(Error)
,completed
,interrupted
三種類型出現(xiàn)都會取消Signal的訂閱晃听,這就是Signal的 dispose方法百侧,這個比較關(guān)鍵。map 和 mapError 方法則是轉(zhuǎn)換和轉(zhuǎn)換錯誤能扒,這里就不介紹了佣渴。
Observers (觀察者)
Observer是指任何等待從信號中接收事件的東西。類似于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這個 枚舉屬性见秤,也可以單獨的對某一個狀態(tài)進行訂閱砂竖。
Property (屬性)
Property 一個屬性表現(xiàn)為 PropertyType
協(xié)議(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的當(dāng)前值可以通過獲取 value獲得乎澄。producer
返回一個會一直發(fā)送值變化信號生成者(signal producer ),
<~
運算符是提供了幾種不同的綁定屬性的方式测摔。注意這里綁定的屬性必須是 MutablePropertyType
類型的置济。
property <~ signal
將一個屬性和信號綁定在一起,屬性的值會根據(jù)信號送過來的值刷新避咆。
property <~ producer
會啟動這個producer舟肉,并且屬性的值也會隨著這個產(chǎn)生的信號送過來的值刷新。
property <~ otherProperty
將一個屬性和另一個屬性綁定在一起查库,這樣這個屬性的值會隨著源屬性的值變化而變化路媚。`
文檔的翻譯是這樣的,具體的用法下面會通過demo來介紹樊销。
Actions (動作)
動作用 Action
類型表示整慎,指當(dāng)有輸入時會做一些工作脏款。當(dāng)動作執(zhí)行時,會有0個或者多個值輸出裤园;或者會產(chǎn)生一個失敗撤师。
Action用來處理用戶交互時做一些處理很方便,比如當(dāng)一個按鈕點擊時這種動作拧揽。Action也可以和一個屬性自動關(guān)聯(lián)disabled剃盾。比如當(dāng)一個UI控件的關(guān)聯(lián)Action被設(shè)置成disabled時,這個控件也會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)建一個label 和一個 textfield尽爆。
//property <~ signal 將一個屬性和信號綁定在一起,屬性的值會根據(jù)信號送過來的值刷新读慎。
//property <~ producer 會啟動這個producer漱贱,并且屬性的值也會隨著這個產(chǎn)生的信號送過來的值刷新。
//property <~ otherProperty將一個屬性和另一個屬性綁定在一起贪壳,這樣這個屬性的值會隨著源屬性的值變化而變化饱亿。
//DynamicProperty 類型用于橋接OC的要求KVC或者KVO的API,比如 NSOperation闰靴。要提醒的是大部分AppKit和UIKit的屬性都不支持KVO,所以要觀察它們值的變化需要通過其他的機制钻注。相比 DynamicProperty要優(yōu)先使用 MutablePropertyType類型蚂且。
label.reactive.text <~ textField.reactive.continuousTextValues
可以通過Signal將 label的text 跟 textfield的輸入內(nèi)容綁定。
下面我們再看有一個實時搜索功能的demo
/// 下面的demo可以通過RAC來實現(xiàn) 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
})
})
根據(jù)textField的 輸入內(nèi)容進行網(wǎng)絡(luò)請求
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(緩沖) 對信號進行操作杏死。
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
}
}
對信號 進行訂閱,就可以得到網(wǎng)絡(luò)請求得到的數(shù)據(jù)捆交,可以用于進行后續(xù)操作淑翼。
最后的話
總的來說ReactiveCocoa的學(xué)習(xí)難度還是很大的,當(dāng)初OC也是花了將近一個月才慢慢理解ReactiveCocoa的用法品追。
大家共勉吧玄括,后續(xù)可能也會有一些學(xué)習(xí)的記錄。這邊文章只是拋磚引玉的記錄了一些文檔中的用法肉瓦。