天天看點

ReactiveCocoa了解(持續更新中)

引子

        ReactiveCocoa 是 OC 的一個強大的架構。它的強大之處不僅僅在于提供了很多用于簡化工作的方法,更在于它提供了一種思考方式。比如這樣的場景:一個登入界面,有使用者名文本框、密碼框以及登入按鈕。隻有使用者名文本框文本長度大于等于6并且密碼框文本長度大于等于6時,登入按鈕才能被點選。按照普遍的實作方式是:每當文本框或密碼框文本發生變化時,都檢查登入按鈕此時是否可被點選。這種方法将精力集中在通過各種途徑來滿足目前的需求。 而 ReactiveCocoa 中的解決方案是:登入按鈕的可被點選=文本框文本長度>=6 并且 密碼框文本長度>=6。這種方法則将精力集中在問題本身,想的是怎麼把這個需求描述清楚,這跟咱們人本身的思維方式保持一緻,也就更易了解了。下面會有更詳細的描述。

信号

        ReactiveCocoa(RAC)是一個函數式反應程式設計(Functional reactive programming)的 Objective-C 架構。啥是函數式反應程式設計?了解了這個東東才能更好地使用 RAC。

        目前的程式設計世界中,編輯語言最多是:面向過程(如 C)、面向對象(如 C++/Java),這兩者不是現在的主題,在此不做界面。響應式程式設計與以上兩種不同,它是面向信号流的。信号流,由一系列信号組成,而信号則包含一些對象,是資訊的載體。概念不多說,舉例可就明白多了。

        比如咱們現在有這樣的任務:站在路邊觀察過來的車輛。當一輛車子過來時,我就産生一個信号,這個信号裡包含了一輛車子的資訊。當一輛輛車子過來,那麼一個個信号也就産生了,這一系列信号就組成了信号流。哈哈,是不是很簡單?了解了的話,恭喜你,你已經掌握了核心部分啦。不過,這也太簡單了點,簡單到這東東有啥用呢?先别急,咱們繼續(我廢話好多-_-!)。

        我做的事情是觀察過來的車輛,就叫車輛觀察員吧。現在又來一人,他的任務是觀察過來車輛時的時候點,記錄每輛車過來的時間,就叫他時間記錄員。這個時間記錄員完全沒必要再去觀察過來的車輛,因為有我這車輛觀察員在呢,隻需在車子來時,我告訴他一聲,來車子了就行,然後他就檢視此時的時間,再記錄下來。這時,時間記錄員就産生了一個新的信号,這個信号包含一個時間資訊。注意,這個時間信号産自于我的車輛信号,即車輛信号映射(map)出一個時間信号。

        又來一人,此人是飛機觀察員,他的任務是觀察天上飛過的飛機。當一架飛機飛過來時,就産生了一個信号,這個信号裡包含這架飛機的資訊。

        再一人,任務是當有飛機飛過時,就寫下“airplane”;當車輛過來時,就寫下“car”。當有飛機飛過或車輛過來時,就産生了一個信号,信号裡的資訊是一個字元串。這個字元串信号産自于我的車輛信号,以及飛機觀察員的飛機信号,也就是将兩個信号給組合(combine)成一個字元串信号。

        映射群組合是信号操作的最基礎的方式。我們可以看到,我的觀察車輛信号可以被多次使用,車輛信号隻要我産生一次,大家就都能使用了,他們就沒有必要再去觀察車輛了,隻要關注自己的任務就行,避免了重複勞動。這說明信号是可以被多次使用的,映射或組合操作是産生新的信号,且并不會影響原來的信号。

        通過映射或組合可以衍生出多種操作,比如過濾(filter)。比如一個奔馳觀察員,當我的車輛信号發生時,我把這個資訊告訴奔馳觀察員,然後此人就根據這時的車輛的牌子來判斷,如果是奔馳車,則生成車輛信号;如果不是奔馳,則啥也不做。這時,奔馳觀察員産生的也是車輛信号,而且所有的車輛都是奔馳車。我産生的車輛則是所有牌子的車。這就是過濾,這實際上是通過映射産生的新信号(不是我這個車輛觀察員的每個信号都産生一個新信号,而是隻針對奔馳車這一特定資訊來産生新信号。其它的信号,則被忽略了)。

信号狀态

        在 RAC 中用RACSignal來代表信号流。signal有三種狀态:正常狀态、完成狀态、錯誤狀态。

  • 正常狀态:正在等待下一信号的來臨。(next)
  • 完成狀态:任務完成了,不會再繼續産生信号。比如我這車輛觀察員下班後,任務已完成,即使再有車輛過來,我也會置之不理。(complete)
  • 錯誤狀态:生成了錯誤,不會再繼續産生信号。比如我突然生病,得去醫院看病了,當然也就不會再繼續觀察過來車輛了。(error)

信号訂閱

        信号有三種狀态,如果我們對信号的某種感興趣,就可以對其訂閱:“嗨我對你的下一信号感有興趣,如果發生了,記得通知我一下”,而我們要做的就是隻要定義當新的信号産生時,要執行的 block 即可:

[signal subscribeNext:^(id value){
    /*
     *  這裡就是對 signal的的 Next 訂閱,每當其有信号産生時,這個 block 就會被調用
     *  這個 block 的參數:value 就是信号所攜帶的資訊資料。
     */
  }];
           

        比如對我的觀察的車輛信号訂閱:

RACSignal *signalCar = 生成觀察車輛的信号
  [signalCar subscribeNext:^(id car) {
    NSLog(“%@“, car); // 每當signalCar 有新的信号産生時,這裡就會列印 car 的資訊。
  }];
           

        與信号的三種狀态相對應,有三個訂閱方法:

- subscribeNext:(void (^)(id x))nextBlock;
  - subscribeCompleted:(void (^)(void))completedBlock;
  - subscribeError:(void (^)(NSError *error))errorBlock;
           

        這裡刻意忽略掉了傳回值,暫時你也不需要了解它,這并不影響你的使用。

RAC 提供的幾個常用方法

        本文的目的在于引領入門,是以這裡隻會介紹幾個常用的方法,讓你能稍微感受一下其魅力。RAC 的方法基本以 rac 開頭,接下來你就會看到RAC 對 UIKit 裡提供了很多很友善的支援,來看看:

a)UIAlertView+RACSignalSupport

        RAC為 UIAlertView 提供了- (RACSignal *)rac_buttonClickedSignal;方法,此方法是為警告框的按鈕點選事件建立了一個signal。

用法如下:

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:@"rac demo" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
  [[alertView rac_buttonClickedSignal] subscribeNext:^(id x) {
    if ([x integerValue] == 1) {
      NSLog(@"點選了确定");
    } else {
      NSLog(@"點選了取消");
    }
  }];
  [alertView show];
           

        是不是很爽?媽媽再也不用擔心我寫各種煩人的 delegate 了!RAC也為UIActionSheet類提供了該方法。

b)UITextField (RACSignalSupport)

        RAC為 UITextFiled 提供了- (RACSignal *)rac_textSignal;方法。這個方法為文本框建立了一個 signal,每當這個文本框的文本改變時,這個 signal 就會 send next。

        比如:

[textField rac_textSignal] subscribeNext:^(NSString *newText) {
    NSLog(@“%@”, newText);
  }];
           
RAC也為 UITextView 提供了該方法。

c)NSNotificationCenter (RACSupport)

        RAC 為NSNotificationCenter提供了方法:- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object;這個 signal在每次相應通知發出時,會 send這個通知相關的NSNotification。 比如:

[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification *keyboardWillShowNotifcation) {
    NSLog(@"鍵盤展現通知");
  }];
           

        啊,終于再也不用為各個通知取名字了!

d) 觀察者支援

        RAC 提供了一個簡單的觀察者宏定義:

RACObserve(TARGET, KEYPATH)
           

        這個宏定義建立了一個 signal,這個 signal 會在 TARGET的 KEYPATH 發生改變時,send next。比如:

RACSignal *signalPersonName = RACObserve(self, person.name);
  @weakify(self);
  [signalPersonName subscribeNext:^(NSString *newName) {
    @strongify(self);
    self.lblName.text = newName;
  }];
           

        對,Observes在 RAC 的世界裡就是這麼簡單,甚至連removeObserver...都不需要。

        尼瑪呀,原來Observes還可以寫得這麼簡單,抓狂吧!終于可以遠離煩人的addObserver和removeObserver了。

        上面的的@weakify(self)以及@strongify(self)是為了避免在 block 中的循環引用 self 的問題。

__weak __typeof__(self) __weak_self = self; // @weakify(self)的實際内容  
__strong __typeof__(self) self = __weak_self; // @strongify(self)的實際内容
           

        具體可問度娘、google。

e)RAC宏

        這是最讓我佩服 RAC 的地方了!這是一個神奇的宏。

        RAC(TARGET, …) 它有兩種方式:

- RAC(TARGET, KEYPATH, NILVALUE)
- RAC(TARGET, KEYPATH)
           

        這個宏将一個信号流與一個對象的屬性綁在一起,當這個 signal 有新的信号時即 next,這個 next 中的對象value将會被自動指派到 target 的 keypath 中。第一種方法則帶有 nilValue,這是在 next 中的對象value為 nil時,會被指派給 target 的 keypath 的值。

        例:

RAC(self, objectProperty) = objectSignal;
RAC(self, stringProperty, @"foobar") = stringSignal;
RAC(self, integerProperty, @42) = integerSignal;
           

        是的,單從這裡,還看不到有什麼神奇的地方,但是當 RAC 與 MVVM 相結合的時候,全得依靠它,這簡直是絕配。