RAC在iOS的實際開發中確實是一件有力的武器,此文將從以下幾方面講解
RACSignal
RACSubject
RACSequence
RACMulticastConnection
RACCommand
RAC常用宏
RAC-bind
RAC-過濾
RAC-映射
RAC-組合
RAC+MVVM-網絡請求
RACSignal
// 1.創建信號RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {// 3.發送信號[subscriber sendNext:@"ws"];// 4.取消信號,如果信號想要被取消,就必須返回一個RACDisposable// 信號什么時候被取消:1.自動取消,當一個信號的訂閱者被銷毀的時候機會自動取消訂閱,2.手動取消,//block什么時候調用:一旦一個信號被取消訂閱就會調用//block作用:當信號被取消時用于清空一些資源return[RACDisposable disposableWithBlock:^{NSLog(@"取消訂閱");? ? ? ? }];? ? }];// 2. 訂閱信號//subscribeNext// 把nextBlock保存到訂閱者里面// 只要訂閱信號就會返回一個取消訂閱信號的類RACDisposable *disposable = [signal subscribeNext:^(idx) {// block的調用時刻:只要信號內部發出數據就會調用這個blockNSLog(@"======%@", x);? ? }];// 取消訂閱[disposable dispose];
總結
.核心:
.核心:信號類
.信號類的作用:只要有數據改變就會把數據包裝成信號傳遞出去
.只要有數據改變就會有信號發出
.數據發出,并不是信號類發出,信號類不能發送數據
.使用方法:
.創建信號
.訂閱信號
.實現思路:
.當一個信號被訂閱,創建訂閱者,并把nextBlock保存到訂閱者里面。
.創建的時候會返回 [RACDynamicSignal createSignal:didSubscribe];
.調用RACDynamicSignal的didSubscribe
.發送信號[subscriber sendNext:value];
.拿到訂閱者的nextBlock調用
RACSubject
RACSubject 在使用中我們可以完全代替代理,代碼簡介方法。具體代碼請看demo中的RACSubject。
總結
我們完全可以用RACSubject代替代理/通知,確實方便許多
這里我們點擊TwoViewController的pop的時候 將字符串"ws"傳給了ViewController的button的title。
步驟:
1.創建信號
RACSubject *subject = [RACSubject subject];
2.訂閱信號
[subject subscribeNext:^(idx) {// block:當有數據發出的時候就會調用// block:處理數據NSLog(@"%@",x);}];
3.發送信號
[subject sendNext:value];
注意
RACSubject和RACReplaySubject的區別
RACSubject必須要先訂閱信號之后才能發送信號, 而RACReplaySubject可以先發送信號后訂閱.
RACSubject 代碼中體現為:先走TwoViewController的sendNext,后走ViewController的subscribeNext訂閱
RACReplaySubject 代碼中體現為:先走ViewController的subscribeNext訂閱,后走TwoViewController的sendNext
可按實際情況各取所需。
RACSequence
使用場景---: 可以快速高效的遍歷數組和字典。
NSString*path = [[NSBundlemainBundle] pathForResource:@"flags.plist"ofType:nil];NSArray*dictArr = [NSArrayarrayWithContentsOfFile:path];? ? [dictArr.rac_sequence.signal subscribeNext:^(idx) {NSLog(@"%@", x);? ? } error:^(NSError*error) {NSLog(@"===error===");? ? } completed:^{NSLog(@"ok---完畢");? ? }];也可以使用宏NSDictionary*dict = @{@"key":@1,@"key2":@2};? ? [dict.rac_sequence.signal subscribeNext:^(idx) {NSLog(@"%@", x);NSString*key = x[0];NSString*value = x[1];// RACTupleUnpack宏:專門用來解析元組// RACTupleUnpack 等會右邊:需要解析的元組 宏的參數,填解析的什么樣數據// 元組里面有幾個值,宏的參數就必須填幾個RACTupleUnpack(NSString*key,NSString*value) = x;NSLog(@"%@ %@", key, value);? ? } error:^(NSError*error) {NSLog(@"===error");? ? } completed:^{NSLog(@"-----ok---完畢");? ? }];
RACMulticastConnection
當有多個訂閱者,但是我們只想發送一個信號的時候怎么辦?這時我們就可以用RACMulticastConnection,來實現。代碼示例如下
// 普通寫法, 這樣的缺點是:沒訂閱一次信號就得重新創建并發送請求,這樣很不友好RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {// didSubscribeblock中的代碼都統稱為副作用。// 發送請求---比如afnNSLog(@"發送請求啦");// 發送信號[subscriber sendNext:@"ws"];returnnil;? ? }];? ? [signal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];? ? [signal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];? ? [signal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];
// 比較好的做法。 使用RACMulticastConnection,無論有多少個訂閱者,無論訂閱多少次,我只發送一個。// 1.發送請求,用一個信號內包裝,不管有多少個訂閱者,只想發一次請求RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {// 發送請求NSLog(@"發送請求啦");// 發送信號[subscriber sendNext:@"ws"];returnnil;? ? }];//2. 創建連接類RACMulticastConnection *connection = [signal publish];? ? [connection.signal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];? ? [connection.signal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];? ? [connection.signal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];//3. 連接。只有連接了才會把信號源變為熱信號[connection connect];
RACCommand
RACCommand:RAC中用于處理事件的類,可以把事件如何處理,事件中的數據如何傳遞,包裝到這個類中,他可以很方便的監控事件的執行過程,比如看事件有沒有執行完畢
使用場景:監聽按鈕點擊,網絡請求
// 普通做法// RACCommand: 處理事件// 不能返回空的信號// 1.創建命令RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(idinput) {//block調用,執行命令的時候就會調用NSLog(@"%@",input);// input 為執行命令傳進來的參數// 這里的返回值不允許為nilreturn[RACSignal createSignal:^RACDisposable *(id subscriber) {? ? ? ? ? ? [subscriber sendNext:@"執行命令產生的數據"];returnnil;? ? ? ? }];? ? }];// 如何拿到執行命令中產生的數據呢?// 訂閱命令內部的信號// ** 方式一:直接訂閱執行命令返回的信號// 2.執行命令RACSignal *signal =[command execute:@2];// 這里其實用到的是replaySubject 可以先發送命令再訂閱// 在這里就可以訂閱信號了[signal subscribeNext:^(idx) {NSLog(@"%@",x);? ? }];
// 一般做法// 1.創建命令RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(idinput) {//block調用,執行命令的時候就會調用NSLog(@"%@",input);// input 為執行命令傳進來的參數// 這里的返回值不允許為nilreturn[RACSignal createSignal:^RACDisposable *(id subscriber) {? ? ? ? ? ? [subscriber sendNext:@"執行命令產生的數據"];returnnil;? ? ? ? }];? ? }];// 方式二:// 訂閱信號// 注意:這里必須是先訂閱才能發送命令// executionSignals:信號源,信號中信號,signalofsignals:信號,發送數據就是信號[command.executionSignals subscribeNext:^(RACSignal *x) {? ? ? ? [x subscribeNext:^(idx) {NSLog(@"%@", x);? ? ? ? }];//? ? ? ? NSLog(@"%@", x);}];// 2.執行命令[command execute:@2];
// 高級做法// 1.創建命令RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(idinput) {// block調用:執行命令的時候就會調用NSLog(@"%@", input);// 這里的返回值不允許為nilreturn[RACSignal createSignal:^RACDisposable *(id subscriber) {? ? ? ? ? ? [subscriber sendNext:@"發送信號"];returnnil;? ? ? ? }];? ? }];// 方式三// switchToLatest獲取最新發送的信號,只能用于信號中信號。[command.executionSignals.switchToLatest subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 2.執行命令[command execute:@3];
// switchToLatest--用于信號中信號// 創建信號中信號RACSubject *signalofsignals = [RACSubject subject];? ? RACSubject *signalA = [RACSubject subject];// 訂閱信號//? ? [signalofsignals subscribeNext:^(RACSignal *x) {//? ? ? ? [x subscribeNext:^(id x) {//? ? ? ? ? ? NSLog(@"%@", x);//? ? ? ? }];//? ? }];// switchToLatest: 獲取信號中信號發送的最新信號[signalofsignals.switchToLatest subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 發送信號[signalofsignals sendNext:signalA];? ? [signalA sendNext:@4];
// 監聽事件有沒有完成//注意:當前命令內部發送數據完成,一定要主動發送完成// 1.創建命令RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(idinput) {// block調用:執行命令的時候就會調用NSLog(@"%@", input);// 這里的返回值不允許為nilreturn[RACSignal createSignal:^RACDisposable *(id subscriber) {// 發送數據[subscriber sendNext:@"執行命令產生的數據"];// *** 發送完成 **[subscriber sendCompleted];returnnil;? ? ? ? }];? ? }];// 監聽事件有沒有完成[command.executing subscribeNext:^(idx) {if([x boolValue] ==YES) {// 正在執行NSLog(@"當前正在執行%@", x);? ? ? ? }else{// 執行完成/沒有執行NSLog(@"執行完成/沒有執行");? ? ? ? }? ? }];// 2.執行命令[command execute:@1];
RAC常用宏
RAC有許多強大而方便的宏。如下
// RAC:把一個對象的某個屬性綁定一個信號,只要發出信號,就會把信號的內容給對象的屬性賦值// 給label的text屬性綁定了文本框改變的信號RAC(self.label, text) =self.textField.rac_textSignal;//? ? [self.textField.rac_textSignal subscribeNext:^(id x) {//? ? ? ? self.label.text = x;//? ? }];
/**
*? KVO
*? RACObserveL:快速的監聽某個對象的某個屬性改變
*? 返回的是一個信號,對象的某個屬性改變的信號
*/[RACObserve(self.view, center) subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];
//例 textField輸入的值賦值給label,監聽label文字改變,RAC(self.label, text) =self.textField.rac_textSignal;? ? [RACObserve(self.label, text) subscribeNext:^(idx) {NSLog(@"====label的文字變了");? ? }];
/**
*? 循環引用問題
*/@weakify(self)? ? RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {? ? ? ? @strongify(self)NSLog(@"%@",self.view);returnnil;? ? }];? ? _signal = signal;使用 @weakify(self)和@strongify(self)來避免循環引用
/**
* 元祖
* 快速包裝一個元組
* 把包裝的類型放在宏的參數里面,就會自動包裝
*/RACTuple *tuple = RACTuplePack(@1,@2,@4);// 宏的參數類型要和元祖中元素類型一致, 右邊為要解析的元祖。RACTupleUnpack_(NSNumber*num1,NSNumber*num2,NSNumber* num3) = tuple;// 4.元祖// 快速包裝一個元組// 把包裝的類型放在宏的參數里面,就會自動包裝NSLog(@"%@ %@ %@", num1, num2, num3);
RAC-bind
// 1.創建信號RACSubject *subject = [RACSubject subject];// 2.綁定信號RACSignal *bindSignal = [subject bind:^RACStreamBindBlock{// block調用時刻:只要綁定信號訂閱就會調用。不做什么事情,return^RACSignal *(idvalue,BOOL*stop){// 一般在這個block中做事 ,發數據的時候會來到這個block。// 只要源信號(subject)發送數據,就會調用block// block作用:處理源信號內容// value:源信號發送的內容,value = @3;// 如果在這里把value的值改了,那么訂閱綁定信號的值即44行的x就變了NSLog(@"接受到源信號的內容:%@", value);//返回信號,不能為nil,如果非要返回空---則empty或 alloc init。return[RACReturnSignalreturn:value];// 把返回的值包裝成信號};? ? }];// 3.訂閱綁定信號[bindSignal subscribeNext:^(idx) {NSLog(@"接收到綁定信號處理完的信號:%@", x);? ? }];// 4.發送信號[subject sendNext:@"123"];
總結
bind(綁定)的使用思想和Hook的一樣---> 都是攔截API從而可以對數據進行操作,,而影響返回數據。
發送信號的時候會來到30行的block。在這個block里我們可以對數據進行一些操作,那么35行打印的value和訂閱綁定信號后的value就會變了。變成什么樣隨你喜歡嘍。
RAC-過濾
有時候我們想要過濾一些信號,這時候我們便可以用RAC的過濾方法。過濾方法有好多種,如下代碼,從不同情況下進行了分析。
// 跳躍 : 如下,skip傳入2 跳過前面兩個值// 實際用處: 在實際開發中比如 后臺返回的數據前面幾個沒用,我們想跳躍過去,便可以用skipRACSubject *subject = [RACSubject subject];? ? [[subject skip:2] subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];? ? [subject sendNext:@1];? ? [subject sendNext:@2];? ? [subject sendNext:@3];
//distinctUntilChanged:-- 如果當前的值跟上一次的值一樣,就不會被訂閱到RACSubject *subject = [RACSubject subject];? ? [[subject distinctUntilChanged] subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 發送信號[subject sendNext:@1];? ? [subject sendNext:@2];? ? [subject sendNext:@2];// 不會被訂閱
// take:可以屏蔽一些值,去掉前面幾個值---這里take為2 則只拿到前兩個值RACSubject *subject = [RACSubject subject];? ? [[subject take:2] subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 發送信號[subject sendNext:@1];? ? [subject sendNext:@2];? ? [subject sendNext:@3];
//takeLast:和take的用法一樣,不過他取的是最后的幾個值,如下,則取的是最后兩個值//注意點:takeLast 一定要調用sendCompleted,告訴他發送完成了,這樣才能取到最后的幾個值RACSubject *subject = [RACSubject subject];? ? [[subject takeLast:2] subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 發送信號[subject sendNext:@1];? ? [subject sendNext:@2];? ? [subject sendNext:@3];? ? [subject sendCompleted];
// takeUntil:---給takeUntil傳的是哪個信號,那么當這個信號發送信號或sendCompleted,就不能再接受源信號的內容了。RACSubject *subject = [RACSubject subject];? ? RACSubject *subject2 = [RACSubject subject];? ? [[subject takeUntil:subject2] subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 發送信號[subject sendNext:@1];? ? [subject sendNext:@2];? ? [subject2 sendNext:@3];// 1//? ? [subject2 sendCompleted]; // 或2[subject sendNext:@4];
// ignore: 忽略掉一些值//ignore:忽略一些值//ignoreValues:表示忽略所有的值// 1.創建信號RACSubject *subject = [RACSubject subject];// 2.忽略一些值RACSignal *ignoreSignal = [subject ignore:@2];// ignoreValues:表示忽略所有的值// 3.訂閱信號[ignoreSignal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 4.發送數據[subject sendNext:@2];
// 一般和文本框一起用,添加過濾條件// 只有當文本框的內容長度大于5,才獲取文本框里的內容[[self.textField.rac_textSignal filter:^BOOL(idvalue) {// value 源信號的內容return[value length] >5;// 返回值 就是過濾條件。只有滿足這個條件才能獲取到內容}] subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];
RAC-映射
RAC的映射在實際開發中有什么用呢?比如我們想要攔截服務器返回的數據,給數據拼接特定的東西或想對數據進行操作從而更改返回值,類似于這樣的情況下,我們便可以考慮用RAC的映射,實例代碼如下
- (void)map {// 創建信號RACSubject *subject = [RACSubject subject];// 綁定信號RACSignal *bindSignal = [subject map:^id(idvalue) {// 返回的類型就是你需要映射的值return[NSStringstringWithFormat:@"ws:%@", value];//這里將源信號發送的“123” 前面拼接了ws:}];// 訂閱綁定信號[bindSignal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 發送信號[subject sendNext:@"123"];
- (void)flatMap {// 創建信號RACSubject *subject = [RACSubject subject];// 綁定信號RACSignal *bindSignal = [subject flattenMap:^RACStream *(idvalue) {// block:只要源信號發送內容就會調用// value: 就是源信號發送的內容// 返回信號用來包裝成修改內容的值return[RACReturnSignalreturn:value];? ? ? ? ? ? }];// flattenMap中返回的是什么信號,訂閱的就是什么信號(那么,x的值等于value的值,如果我們操縱value的值那么x也會隨之而變)// 訂閱信號[bindSignal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 發送數據[subject sendNext:@"123"];? ? }
- (void)flattenMap2 {// flattenMap 主要用于信號中的信號// 創建信號RACSubject *signalofSignals = [RACSubject subject];? ? RACSubject *signal = [RACSubject subject];// 訂閱信號//方式1//? ? [signalofSignals subscribeNext:^(id x) {////? ? ? ? [x subscribeNext:^(id x) {//? ? ? ? ? ? NSLog(@"%@", x);//? ? ? ? }];//? ? }];// 方式2//? ? [signalofSignals.switchToLatest? ];// 方式3//? RACSignal *bignSignal = [signalofSignals flattenMap:^RACStream *(id value) {////? ? ? ? //value:就是源信號發送內容//? ? ? ? return value;//? ? }];//? ? [bignSignal subscribeNext:^(id x) {//? ? ? ? NSLog(@"%@", x);//? ? }];// 方式4--------也是開發中常用的[[signalofSignals flattenMap:^RACStream *(idvalue) {returnvalue;? ? }] subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 發送信號[signalofSignals sendNext:signal];? ? [signal sendNext:@"123"];}
RAC-組合
把多個信號聚合成你想要的信號,使用場景----:比如-當多個輸入框都有值的時候按鈕才可點擊。
// 思路--- 就是把輸入框輸入值的信號都聚合成按鈕是否能點擊的信號。- (void)combineLatest {? ? ? ? RACSignal *combinSignal = [RACSignal combineLatest:@[self.accountField.rac_textSignal,self.pwdField.rac_textSignal] reduce:^id(NSString*account,NSString*pwd){//reduce里的參數一定要和combineLatest數組里的一一對應。// block: 只要源信號發送內容,就會調用,組合成一個新值。NSLog(@"%@ %@", account, pwd);return@(account.length && pwd.length);? ? }];//? ? // 訂閱信號//? ? [combinSignal subscribeNext:^(id x) {//? ? ? ? self.loginBtn.enabled = [x boolValue];//? ? }];? ? // ----這樣寫有些麻煩,可以直接用RAC宏RAC(self.loginBtn, enabled) = combinSignal;}
- (void)zipWith {//zipWith:把兩個信號壓縮成一個信號,只有當兩個信號同時發出信號內容時,并且把兩個信號的內容合并成一個元祖,才會觸發壓縮流的next事件。// 創建信號ARACSubject *signalA = [RACSubject subject];// 創建信號BRACSubject *signalB = [RACSubject subject];// 壓縮成一個信號// **-zipWith-**: 當一個界面多個請求的時候,要等所有請求完成才更新UI// 等所有信號都發送內容的時候才會調用RACSignal *zipSignal = [signalA zipWith:signalB];? ? [zipSignal subscribeNext:^(idx) {NSLog(@"%@", x);//所有的值都被包裝成了元組}];// 發送信號 交互順序,元組內元素的順序不會變,跟發送的順序無關,而是跟壓縮的順序有關[signalA zipWith:signalB]---先是A后是B[signalA sendNext:@1];? ? [signalB sendNext:@2];}
// 任何一個信號請求完成都會被訂閱到// merge:多個信號合并成一個信號,任何一個信號有新值就會調用- (void)merge {// 創建信號ARACSubject *signalA = [RACSubject subject];// 創建信號BRACSubject *signalB = [RACSubject subject];//組合信號RACSignal *mergeSignal = [signalA merge:signalB];// 訂閱信號[mergeSignal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];// 發送信號---交換位置則數據結果順序也會交換[signalB sendNext:@"下部分"];? ? [signalA sendNext:@"上部分"];}
// then --- 使用需求:有兩部分數據:想讓上部分先進行網絡請求但是過濾掉數據,然后進行下部分的,拿到下部分數據- (void)then {// 創建信號ARACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {// 發送請求NSLog(@"----發送上部分請求---afn");? ? ? ? ? ? ? ? [subscriber sendNext:@"上部分數據"];? ? ? ? [subscriber sendCompleted];// 必須要調用sendCompleted方法!returnnil;? ? }];// 創建信號B,RACSignal *signalsB = [RACSignal createSignal:^RACDisposable *(id subscriber) {// 發送請求NSLog(@"--發送下部分請求--afn");? ? ? ? [subscriber sendNext:@"下部分數據"];returnnil;? ? }];// 創建組合信號// then;忽略掉第一個信號的所有值RACSignal *thenSignal = [signalA then:^RACSignal *{// 返回的信號就是要組合的信號returnsignalsB;? ? }];// 訂閱信號[thenSignal subscribeNext:^(idx) {NSLog(@"%@", x);? ? }];}
// concat----- 使用需求:有兩部分數據:想讓上部分先執行,完了之后再讓下部分執行(都可獲取值)- (void)concat {// 組合// 創建信號ARACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {// 發送請求//? ? ? ? NSLog(@"----發送上部分請求---afn");[subscriber sendNext:@"上部分數據"];? ? ? ? [subscriber sendCompleted];// 必須要調用sendCompleted方法!returnnil;? ? }];// 創建信號B,RACSignal *signalsB = [RACSignal createSignal:^RACDisposable *(id subscriber) {// 發送請求//? ? ? ? NSLog(@"--發送下部分請求--afn");[subscriber sendNext:@"下部分數據"];returnnil;? ? }];// concat:按順序去鏈接//**-注意-**:concat,第一個信號必須要調用sendCompleted// 創建組合信號RACSignal *concatSignal = [signalA concat:signalsB];// 訂閱組合信號[concatSignal subscribeNext:^(idx) {NSLog(@"%@",x);? ? }];}
作者:帥小王
鏈接:http://www.lxweimin.com/p/db772bbb6a2f
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。