這是RAC最核心的內容,若是用插頭和插座來描述,插座是Signal,插頭是Subscriber。插座負責去獲取電,插頭負責使用電,並且一個插座能夠插任意數量的插頭。當一個插座(Signal)沒有插頭 (Subscriber)時什麼也不幹,也就是處於冷(Cold)的狀態,只有插了插頭時纔會去獲取,這個時候就處於熱(Hot)的狀態。react
Signal獲取到數據後,會調用Subscriber的sendNext, sendComplete, sendError方法來傳送數據給Subscriber,Subscriber天然也有方法來獲取傳過來的數據,如:[signal subscribeNext:error:completed]。這樣只要沒有sendComplete和sendError,新的值就會經過 sendNext源源不斷地傳送過來。git
RACObserve
使用了KVO來監聽property的變化,只要username被本身或外部改變,block就會被執行。但不是全部的property均可以被RACObserve
,該property必須支持KVO,好比NSURLCache的currentDiskUsage就不能被RACObserve
。github
Signal是很靈活的,它能夠被修改(map),過濾(filter),疊加(combine),串聯(chain),這有助於應對更加複雜的狀況,好比:編程
RAC(self.logInButton, enabled) = [RACSignal combineLatest:@[ self.usernameTextField.rac_textSignal, self.passwordTextField.rac_textSignal, RACObserve(LoginManager.sharedManager, loggingIn), RACObserve(self, loggedIn) ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) { return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue); }];
左邊的RAC(...)
,它的做用是將self.logInButton.enabled
屬性與右邊的signal的sendNext值綁定。也就是若是右邊的reduce的返回值爲NO,那麼enabled就爲NO。右邊的combineLatest
是獲取這4個signal的next值。其中能夠看到self.usernameTextField.rac_textSignal
這麼個東東,rac_textSignal
是 RAC爲UITextField添加的category,只要usernameTextField的值有變化,這個值就會被返回(sendNext)。 combineLatest須要每一個signal至少都有過一次sendNext。reduce的做用是根據接收到的值,再返回一個新的值,這裏是 @(YES)和@(NO),必須是object。vim
RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) { NSLog(@"triggered"); [subscriber sendNext:@"foobar"]; [subscriber sendCompleted]; return nil; }];
建立了一個Signal,但由於沒有被subscribe,因此什麼也不會發生。因此此時處於冷信號狀態api
[signal subscribeCompleted:^{ NSLog(@"subscription %u", subscriptions); }];
加了上面這段代碼後,signal就處於Hot的狀態了,block裏的代碼就會被執行。緩存
或許有人會問,若是這時又有一個新的subscriber了,signal的block還會被執行嗎?這就牽扯到了另外一個概念:Side Effect數據結構
仍是上面那段代碼,若是有多個subscriber,那麼signal就會又一次被觸發,控制檯裏會輸出兩次triggered
。這或許是你想要的,或許不是。若是要避免這種狀況的發生,可使用[ signal replay
]方法,它的做用是保證signal只被觸發一次,而後把sendNext的value存起來,下次再有新的subscriber時,直接發送緩存的數據。架構
爲了更加方便地使用RAC,RAC給Cocoa添加了不少category,與系統集成地越緊密,使用起來天然也就越方便。下面是我認爲比較經常使用的categories。併發
UIView Categories
上面看到的rac_textSignal
是加在UITextField上的(UITextField+RACSignalSupport.h),其餘經常使用的UIView也都有添加相應的category,好比UIAlertView,就不須要再用Delegate了。
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"" message:@"Alert" delegate:nil cancelButtonTitle:@"YES" otherButtonTitles:@"NO", nil]; [[alertView rac_buttonClickedSignal] subscribeNext:^(NSNumber *indexNumber) { if ([indexNumber intValue] == 1) { NSLog(@"you touched NO"); } else { NSLog(@"you touched YES"); } }]; [alertView show];
有了這些Category,大部分的Delegate均可以使用RAC來作。或許你會想,可不能夠subscribe NSMutableArray.rac_sequence.signal,這樣每次有新的object或舊的object被移除時都能知 道,UITableViewController就能夠根據dataSource的變化,來reloadData。但很惋惜這樣不行,由於RAC是基於 KVO的,而NSMutableArray並不會在調用addObject或removeObject時發送通知,因此不可行。不過可使用 NSArray做爲UITableView的dataSource,只要dataSource有變更就換成新的Array,這樣就能夠了。
說到UITableView,再說一下UITableViewCell,RAC給UITableViewCell提供了一個方法:rac_prepareForReuseSignal
,它的做用是當Cell即將要被重用時,告訴Cell。想象Cell上有多個button,Cell在初始化時給每一個button都addTarget:action:forControlEvents
,被重用時須要先移除這些target,下面這段代碼就能夠很方便地解決這個問題:
[[[self.cancelButton rac_signalForControlEvents:UIControlEventTouchUpInside] takeUntil:self.rac_prepareForReuseSignal] subscribeNext:^(UIButton *x) { // do other things }];
還有一個很經常使用的category就是UIButton+RACCommandSupport.h
,它提供了一個property:rac_command
,就是當button被按下時會執行的一個命令,命令被執行完後能夠返回一個signal,有了signal就有了靈活性。好比點擊投票按鈕,先判斷一下有沒有登陸,若是有就發HTTP請求,沒有就彈出登錄框,能夠這麼實現。
logInButton.rac_command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal empty];
}];
//下面的代碼摘自AshFurrow的FunctionalReactivePixels。
voteButton.rac_command = [[RACCommand alloc] initWithEnabled:self.viewModel.voteCommand.enabled signalBlock:^RACSignal *(id input) { // Assume that we're logged in at first. We'll replace this signal later if not. RACSignal *authSignal = [RACSignal empty]; if ([[PXRequest apiHelper] authMode] == PXAPIHelperModeNoAuth) { // Not logged in. Replace signal. authSignal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { @strongify(self); FRPLoginViewController *viewController = [[FRPLoginViewController alloc] initWithNibName:@"FRPLoginViewController" bundle:nil]; UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; [self presentViewController:navigationController animated:YES completion:^{ [subscriber sendCompleted]; }]; return nil; }]]; } return [authSignal then:^RACSignal *{ @strongify(self); return [[self.viewModel.voteCommand execute:nil] ignoreValues]; }]; }]; [voteButton.rac_command.errors subscribeNext:^(id x) { [x subscribeNext:^(NSError *error) { [SVProgressHUD showErrorWithStatus:[error localizedDescription]]; }]; }];
經常使用的數據結構,如NSArray, NSDictionary也都有添加相應的category,好比NSArray
添加了rac_sequence
,能夠將NSArray
轉換爲RACSequence
,順便說一下RACSequence
, RACSequence
是一組immutable且有序的values,不過這些values是運行時計算的,因此對性能提高有必定的幫助。RACSequence
提供了一些方法,如array
轉換爲NSArray
,any:
檢查是否有Value符合要求,all:
檢查是否是全部的value都符合要求,這裏的符合要求的,block返回YES,不符合要求的就返回NO。
NSNotificationCenter
, 默認狀況下NSNotificationCenter
使用Target-Action
方式來處理Notification,這樣就須要另外定義一個方法,這就涉及到編程領域的兩大難題之一:起名字。有了RAC,就有Signal,有了Signal就能夠subscribe,因而NotificationCenter
就能夠這麼來處理,還不用擔憂移除observer的問題。
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"MyNotification" object:nil] subscribeNext:^(NSNotification *notification) { NSLog(@"Notification Received"); }];
NSObject有很多的Category,我以爲比較有用的有這麼幾個
顧名思義就是在一個object的dealloc被觸發時,執行的一段代碼。
NSArray *array = @[@"foo"]; [[array rac_willDeallocSignal] subscribeCompleted:^{ NSLog(@"oops, i will be gone"); }]; array = nil;
有時咱們但願知足必定條件時,自動觸發某個方法,有了這個category就能夠這麼辦
- (void)test { RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [subscriber sendNext:@"A"]; }); return nil; }]; RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"B"]; [subscriber sendNext:@"Another B"]; [subscriber sendCompleted]; return nil; }]; [self rac_liftSelector:@selector(doA:withB:) withSignals:signalA, signalB, nil]; } - (void)doA:(NSString *)A withB:(NSString *)B { NSLog(@"A:%@ and B:%@", A, B); }
這裏的rac_liftSelector:withSignals
就是幹這件事的,它的意思是當signalA和signalB都至少sendNext過一次,接下來只要其中任意一個signal有了新的內容,doA:withB
這個方法就會自動被觸發。
若是你有興趣,能夠想一想上面這段代碼會輸出什麼。
這個category有rac_signalForSelector:
和rac_signalForSelector:fromProtocol:
這兩個方法。先來看前一個,它的意思是當某個selector被調用時,再執行一段指定的代碼,至關於hook。好比點擊某個按鈕後,記個日誌。後者表示該selector實現了某個協議,因此能夠用它來實現Delegate。
RAC帶來的變化還不只僅是這些,它還帶來了架構層面的變化。咱們都知道蘋果推薦的是MVC架構,那MVVM又是什麼呢?
跟MVC最大的區別是多了個ViewModel
,它直接與View綁定,並且對View一無所知。拿作菜打比方的話,ViewModel就是調料,它不關心作的究竟是什麼菜。這不是跟Model
很像嗎?是的,它能夠扮演Model的職責,但其實它是Model的中介,這樣當Model的API有變化,或者由本地存儲變爲遠程API調用時,ViewModel的public API能夠保持不變。
使用ViewModel的好處是,可讓Controller更加簡單和輕便,並且ViewModel相對獨立,也更加方便測試和重用。那 Controller這時又該作哪些事呢?在MVVM體系中,Controller能夠被當作View,因此它的主要工做是處理佈局、動畫、接收系統事 件、展現UI。
MVVM還有一個很重要的概念是 data binding
,view的呈現須要data,這個data就是由ViewModel提供的,將view的data與ViewModel的data綁定後,未來雙方的數據只要一方有變化,另外一方就能收到。這裏有Github 開源的一個ViewModel Base Class。
RAC在使用時有一些注意事項,能夠參考官方的DesignGuildLines,這裏簡單說一下。
當一個signal被一個subscriber subscribe後,這個subscriber什麼時候會被移除?答案是當subscriber被sendComplete或sendError時,或者手動調用[disposable dispose]。
當subscriber被dispose後,全部該subscriber相關的工做都會被中止或取消,如http請求,資源也會被釋放。
Signal events是線性的,不會出現併發的狀況,除非顯示地指定Scheduler。因此-subscribeNext:error:completed:
裏的block不須要鎖定或者synchronized等操做,其餘的events會依次排隊,直到block處理完成。
Errors有優先權,若是有多個signals被同時監聽,只要其中一個signal sendError,那麼error就會馬上被傳送給subscriber,並致使signals終止執行。至關於Exception。
生成Signal時,最好指定Name, -setNameWithFormat:
方便調試。
block代碼中不要阻塞。
實例
例1. 監聽對象的成員變量變化,當成員變量值被改變時,觸發作一些事情。
場景1:當前類有一個屬性 NSString *input,當它的值被改變時,發送一個請求。
[RACObserve(self, input) subscribeNext:^(id x){ request(x);//發送一個請求 }];
場景2:當前類有一個屬性textField,當它的Text值被改變時,發送一個請求。
[RACObserve(self, textField.text) subscribeNext:^(id x) { request(x);//發送一個請求 }];
場景1和2 注意:RACObserve(self, textField.text)中的第二參數必定要和當前類的屬性相關,不能是全局變量或者局部變量。subscribeNext:^(id x)中就是RACObserve(self, textField.text)中的第二參數即textField.text。
場景3:在上面場景中,當用戶輸入的值以2開頭時,才發請求.
[[RACObserve(self, input) filter:^(NSString* value){ if ([value hasPrefix:@"2"]) { return YES; } else { return NO; } }] subscribeNext:^(NSString* x){ request(x);//發送一個請求 }];
場景4:面場景是監聽本身的成員變量,若是想監聽UITextField輸入值變化,框架也作了封裝能夠代替系統回調
[[self.priceInput.rac_textSignal filter:^BOOL(NSString *str) { if (str.integerValue > 20) { return YES; } else { return NO; } }] subscribeNext:^(NSString *str) { request(x);//發送一個請求 }];
例2. 同時監聽多個變量變化,當這些變量知足必定條件時,使button爲可點擊狀態
場景1:button監聽 兩個輸入框有值和一個屬性Bool變量值,當輸入框有輸入且Bool爲真時,button爲可點擊狀態
RAC(self.payButton,enabled) = [RACSignal combineLatest:@[self.priceInput.rac_textSignal, self.nameInput.rac_textSignal, RACObserve(self, isConnected) ] reduce:^(NSString *price, NSString *name, NSNumber *connect){ return @(price.length > 0 && name.length > 0 && [connect boolValue]); }];
場景1注意: combineLatest: reduce:中的關係層次和返回值.
場景2:知足上面條件時,直接發送請求
[[RACSignal combineLatest:@[self.priceInput.rac_textSignal, self.nameInput.rac_textSignal, RACObserve(self, isConnected) ] reduce:^(NSString *price, NSString *name, NSNumber *connect){ return @(price.length > 0 && name.length > 0 && ![connect boolValue]); }] subscribeNext:^(NSNumber *res){ if ([res boolValue]) { NSLog(@"XXXXX send request"); } }];
場景4:用戶每次在TextField中輸入一個字符,1秒內沒有其它輸入時,去發一個請求。TextField中字符改變觸發事件已在例1中展現,這裏實現一下它觸法的方法,把1秒延時在此方法中實現。
- (void)showLoading { [self.loadingDispose dispose];//上次信號還沒處理,取消它(距離上次生成還不到1秒) @weakify(self); self.loadingDispose = [[[RACSignal createSignal:^RACDisposable *(id<racsubscriber> subscriber) { [subscriber sendCompleted]; return nil; }] delay:1] //延時一秒 subscribeCompleted:^{ @strongify(self); doRequest(); self.loadingDispose = nil; }]; }