RAC基礎筆記(2)

ReactiveCocoa 常見類

信號源相關

RACStream

An abstract class representing any stream of values.編程

RACStream 是一個抽象類,是以 Monad (函數式編程語言)的概念爲依據進行設計的,它表明的就是一個 Monad 。有了 Monad 做爲基石後,許多基於流的操做就能夠被創建起來了,好比map 、 filter 、 zip 等。緩存

RACSignal

能夠把信號想象成水龍頭,只不過裏面不是水,而是玻璃球(value),直徑跟水管的內徑同樣,這樣就能保證玻璃球是依次排列,不會出現並排的狀況(數據都是線性處理的,不會出現併發狀況)。水龍頭的開關默認是關的,除非有了接收方(subscriber),纔會打開。這樣只要有新的玻璃球進來,就會自動傳送給接收方。能夠在水龍頭上加一個過濾嘴(filter),不符合的不讓經過,也能夠加一個改動裝置,把球改變成符合本身的需求(map)。也能夠把多個水龍頭合併成一個新的水龍頭(combineLatest:reduce:),這樣只要其中的一個水龍頭有玻璃球出來,這個新合併的水龍頭就會獲得這個球。閉包

RACSignal 表明的是未來會被傳遞的值,它是一種 push-driven 的流。 RACSignal 能夠向訂閱者發送三種不一樣類型的事件:併發

  • next : RACSignal 經過 next 事件向訂閱者傳送新的值,而且這個值能夠爲 nil ;
  • error : RACSignal 經過 error 事件向訂閱者代表信號在正常結束前發生了錯誤;
  • completed : RACSignal 經過 completed 事件向訂閱者代表信號已經正常結束,不會再有後續的值傳送給訂閱者。

注意, ReactiveCocoa 中的值流只包含正常的值,即經過 next 事件傳送的值,並不包括 error 和 completed 事件,它們須要被特殊處理。一般狀況下,一個信號的生命週期是由任意個next 事件和一個 error 事件或一個 completed 事件組成的。less

RACSignal 的 Subscription 過程歸納起來能夠分爲三個步驟:編程語言

  1. [RACSignal createSignal] 來得到 signal
  2. [signal subscribeNext:] 來得到 subscriber,而後進行subscription
  3. 進入 didSubscribe ,經過 [subscriber sendNext:] 來執行 next block 。

RACSubject

RACSubject 表明的是能夠手動控制的信號,咱們能夠把它看做是 RACSignal 的可變版本,就比如 NSMutableArray 是 NSArray 的可變版本同樣。 RACSubject 繼承自 RACSignal ,因此它能夠做爲信號源被訂閱者訂閱,同時,它又實現了 RACSubscriber 協議,因此它也能夠做爲訂閱者訂閱其餘信號源,這個就是 RACSubject 爲何能夠手動控制的緣由。ide

實際使用中,在 MVVM 中使用 RACSubject 能夠很是方便地實現統一的錯誤處理邏輯。好比,咱們能夠在 viewModel 的基類中聲明一個 RACSubject 類型的屬性 errors ,而後在 viewController 的基類中編寫統一的錯誤處理邏輯:函數式編程

[self.viewModel.errors subscribeNext:^(NSError *error) {
    // 錯誤處理邏輯
}

RACCommand

A command is a signal triggered in response to some action, typically UI-related.函數

RACCommand 一般用來表示某個 Action 的執行,好比點擊 Button 。它有幾個比較重要的屬性:executionSignals / errors / executing。oop

  • executionSignals :是 signal of signals ,若是直接 subscribe 的話會獲得一個 signal ,而不是咱們想要的 value,因此通常會配合 switchToLatest 。
  • errors :跟正常的 signal 不同,RACCommand 的錯誤不是經過 sendError 來實現的,而是經過 errors 屬性傳遞出來的。
  • executing :表示該 command 當前是否正在執行。

RACSequence

Represents an immutable sequence of values. Unless otherwise specified, the sequences’ values are evaluated lazily on demand. Like Cocoa collections, sequences cannot contain nil.

RACSequence 表明的是一個不可變的值的序列,與 RACSignal 不一樣,它是 pull-driven 類型的流。從嚴格意義上講, RACSequence 並不能算做是信號源,由於它並不能像 RACSignal 那樣,能夠被訂閱者訂閱,可是它與 RACSignal 之間能夠很是方便地進行轉換。

所以,咱們能夠很是方便地使用 RACSequence 來實現集合的鏈式操做,直到獲得你想要的最終結果爲止,經常使用的使用場景爲「字典轉模型」。

注意: RACSequence 會涉及到性能與效率的問題。

訂閱者相關

RACSubscriber

Represents any object which can directly receive values from a RACSignal.

訂閱者對信號源的一次訂閱過程能夠抽象爲:經過 RACSignal 的 -subscribe: 方法傳入一個訂閱者,並最終返回一個 RACDisposable 對象的過程。

注意:在 ReactiveCocoa 中並無專門的類 RACSubscription 來表明一次訂閱,而間接地使用 RACDisposable 來充當這一角色。所以,一個 RACDisposable 對象就表明着一次訂閱,而且咱們能夠用它來取消此次訂閱。

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock { trueNSCParameterAssert(nextBlock != NULL); true trueRACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL]; truereturn [self subscribe:o]; } + (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed { trueRACSubscriber *subscriber = [[self alloc] init]; truesubscriber->_next = [next copy]; truesubscriber->_error = [error copy]; truesubscriber->_completed = [completed copy]; truereturn subscriber; } - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { trueNSCAssert(NO, @"This method must be overridden by subclasses"); truereturn nil; } 

調度器相關

RACScheduler

Schedulers are used to control when and where work is performed.

RACScheduler 在 ReactiveCocoa 中就是扮演着調度器的角色,本質上,它就是用 GCD 的串行隊列來實現的,而且支持取消操做。是的,在 ReactiveCocoa 中,並無使用到 NSOperationQueue 和 NSRunloop 等技術, RACScheduler 也只是對 GCD 的簡單封裝而已。

清潔工相關

RACDisposable

A disposable encapsulates the work necessary to tear down and cleanup a subscription.

RACDisposable 在 ReactiveCocoa 中就充當着清潔工的角色,它封裝了取消和清理一次訂閱所必需的工做。它有一個核心的方法 -dispose ,調用這個方法就會執行相應的清理工做,這有點相似於 NSObject 的 -dealloc 方法。

ReactiveCocoa 常見用法

代替代理

  • rac_signalForSelector 用戶代替代理

代替 KVO

  • rac_valuesAndChangesForKeyPath 用於監聽某個對象的某個屬性的改變

代替事件監聽

  • rac_signalForControlEvents 用於監聽某個事件

代替通知

  • rac_addObserverForName 用於監聽某個通知,且不須要在 - (void)dealloc 中移除監聽

監聽文本框文字改變

  • rac_textSignal 用於監聽文本框文字變化

代替手勢

  • rac_gestureSignal 用於監聽手勢操做

多個請求完成時,再執行後繼操做

  • rac_liftSelector:withSignalsFromArray:Signals 當傳入的 Signals,每個 Signal 都至少 sendNext 過一次,就會去觸發第一個 selector 參數的方法。

信號的相關操做

  • bind :函數會返回一個新的信號 N。總體思路是對原信號 O 進行訂閱,每當信號 O 產生一個值就將其轉變成一箇中間信號 M ,並立刻訂閱 M ,以後將信號M的輸出做爲新信號 N 的輸出。
  • map \ flattenMap :用於把源信號內容映射成新的內容(信號)。
  • concat :組合,按必定順序拼接信號,當多個信號發出的時候,有順序的接收信號。
  • then :用於鏈接兩個信號,當第一個信號完成,纔會鏈接 then 返回的信號。
  • merge :把多個信號合併爲一個信號,任何一個信號有新值的時候就會調用。
  • zipWith :把兩個信號壓縮成一個信號,只有當兩個信號同時發出信號內容時,而且把兩個信號的內容合併成一個元組,纔會觸發壓縮流的 next 事件。
  • combineLatest :將多個信號合併起來,而且拿到各個信號的最新的值,必須每一個合併的 signal 至少都有過一次 sendNext ,纔會觸發合併的信號。
  • reduce :聚合,用於信號發出的內容是元組,把信號發出元組的值聚合成一個值。
  • filter :過濾信號,使用它能夠獲取知足條件的信號。
  • ignore :忽略某些值的信號,使用 RACObserve 時可配合使用,其實現由 filter 完成。
  • distinctUntilChanged :實現是用 bind 來完成的,每次變換中都記錄一下原信號上一次發送過來的值,並與這一次進行比較,若是是相同的值,就「吞」掉,返回 empty 信號。只有和原信號上一次發送的值不一樣,變換後的新信號才把這個值發送出來。
  • take :從開始一共取 N 次的信號。
  • takeLast :取最後 N 次的信號,前提條件:訂閱者必須調用完成,由於只有完成,才知道總共有多少信號。
  • takeUntil :獲取信號直到某個信號執行完成。
  • skip :跳過幾個信號,不接受。
  • switchToLatest :用於 signalOfSignals (信號的信號),有時候信號也會發出信號,會在 signalOfSignals 中,獲取 signalOfSignals 發送的最新信號。
  • doNext :執行 next 以前,會先執行這個 Block 。
  • doCompleted :執行 sendCompleted 以前,會先執行這個Block 。
  • timeout :超時,可讓一個信號在必定的時間後,自動報錯。
  • interval :定時:每隔一段時間發出信號。
  • delay :延遲發送 next 。
  • retry :重試,只要失敗,就會從新執行建立信號中的 block ,直到成功。
  • replay :重放,當一個信號被屢次訂閱,反覆播放內容。
  • throttle :節流,當某個信號發送比較頻繁時,可使用節流,在某一段時間不發送信號內容,過了一段時間獲取信號的最新內容發出。

ReactiveCocoa 常見宏

  • RAC(TARGET, ...) 用於綁定某個對象的某個屬性
  • RACObserve(TARGET, KEYPATH) 用於監聽某個對象的某個屬性,返回的是信號
  • @weakify(Obj) & @strongify(Obj) 配套使用

注意事項

Side Effect

Side effects occur for each subscription by default, but there are certain situations where side effects should only occur once – for example, a network request typically should not be repeated when a new subscriber is added.

若是某個信號被多個 subscriber 訂閱,那麼它的 didSubscribe 會被屢次調用。

若是想要避免這種狀況的發生,可使用 reply / replayLast / replayLazily 方法,它們的做用是保證 signal 只被觸發一次,而後把 sendNext: 的 value 給緩存起來,下一次再有新的 subscriber 時,直接發送緩存的 value 。

其內部實現依賴: - (RACMulticastConnection *)multicast:(RACSubject *)subject; 這個方法。

Cell 重用

RAC 給 UITableViewCell 提供了一個方法: rac_prepareForReuseSignal ,它的做用是當 Cell 即將要被重用時,告訴 Cell 。想象 Cell 上有多個 Button ,Cell 在初始化時給每一個 Button 都 addTarget:action:forControlEvents ,被重用時須要先移除這些 target ,下面這段代碼就能夠很方便地解決這個問題:

[[[self.cancelButton
truerac_signalForControlEvents:UIControlEventTouchUpInside]
truetakeUntil:self.rac_prepareForReuseSignal]
truesubscribeNext:^(UIButton *x) {
true// do other things }]; 

Strong / Weak Dance

由於 RAC 不少操做都是在 Block 中進行的,因此最多見的問題即是「循環引用」,因此須要經過 @weakify 和 @strongify 來消除循環引用。

注意:事實上 RACObserve(TARGET, KEYPATH) 老是會引用 self ,即便 target 不是 self ,因此只要有 RACObserve 的地方都要使用 @weakify / @strongify 。

flattenMap 與 map 的區別

  • flattenMap 中的 block 返回信號。
  • map 中的 block 返回對象。
  • map 的實現是用了 flattenMap 函數來實現的。把 map 的入參閉包,放到了 flattenMap的返回值中。
  • 開發中,若是信號發出的值不是信號,映射通常使用 map 。
  • 開發中,若是信號發出的值是信號,映射通常使用 flattenMap 。
  • signalOfsignals 用 flattenMap 。

冷信號與熱信號的區別

  • Hot Observable 是主動的,儘管你並無訂閱事件,可是它會時刻推送,就像鼠標移動;而 Cold Observable 是被動的,只有當你訂閱的時候,它纔會發佈消息。
  • Hot Observable 能夠有多個訂閱者,是一對多,集合能夠與訂閱者共享信息;而 Cold Observable 只能一對一,當有不一樣的訂閱者,消息是從新完整發送。
  • Subject 相似「直播」,錯過了就再也不處理。而 Signal 相似「點播」。
  • RACSubject 及其子類是熱信號, RACSignal 排除 RACSubject 類之外的是冷信號。
  • RACSubject 會持有訂閱者(由於 RACSubject 是熱信號,爲了保證將來有事件發送的時候,訂閱者能夠收到信息,因此須要對訂閱者保持狀態,作法就是持有訂閱者),而 RACSignal 不會持有訂閱者。

其餘

  • 當一個 signal 被一個 subscriber subscribe 後,這個 subscriber 什麼時候會被移除?答案是:當 subscriber 被 sendComplete 或 sendError 時,或者手動調用 [disposable dispose] 。

  • replay 是 multicast 的一個特殊 case 而已。

  • 當 subscriber 被 dispose 後,全部該 subscriber 相關的工做都會被中止或取消,如 http 請求,資源也會被釋放。

  • Errors 有優先權,若是有多個 signals 被同時監聽,只要其中一個 signal sendError ,那麼 error 就會馬上被傳送給 subscriber ,並致使 signals 終止執行。至關於 Exception 。

  • 使用 RACSubject ,若是進行了 map 操做,那麼必定要發送完成信號,否則會內存泄漏。

  • 任何的信號轉換便是對原有的信號進行訂閱從而產生新的信號。

相關文章
相關標籤/搜索