ReactiveCocoa代碼實(shí)踐之-更多思考
相關(guān)閱讀:
ReactiveCocoa代碼實(shí)踐之-UI組件的RAC信號(hào)操作
ReactiveCocoa代碼實(shí)踐之-RAC網(wǎng)絡(luò)請(qǐng)求重構(gòu)
1. RACObserve()宏形參寫法的區(qū)別
之前寫代碼考慮過 RACObserve(self.timeLabel , text) 和 RACObserve(self , timeLabel.text) 的區(qū)別。 因?yàn)檫@兩種方法都是觀察self.timeLabel.text的屬性,并且都能實(shí)現(xiàn)功能。估計(jì)是作者原本用的其中一種后來對(duì)另一種也提供了支持,究竟有什么區(qū)別哪一種寫法更好?
點(diǎn)進(jìn)去看RACObserve的源碼 大多都是方法調(diào)用,一層一層點(diǎn)進(jìn)去最后來到這個(gè)方法。
- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)weakObserver block:(void (^)(id, NSDictionary *, BOOL, BOOL))block
這個(gè)方法里面把逗號(hào)后面的keypath通過“.” 進(jìn)行分割成了一個(gè)數(shù)組。 并且得到三個(gè)屬性
BOOL keyPathHasOneComponent = (keyPathComponents.count == ); NSString *keyPathHead = keyPathComponents[]; NSString *keyPathTail = keyPath.rac_keyPathByDeletingFirstKeyPathComponent;
這里會(huì)取到keypatch的頭和去掉頭的部分,并且會(huì)在下面方法內(nèi)部自己調(diào)用自己
// Adds the callback block to the remaining path components on the value. Also // adds the logic to clean up the callbacks to the firstComponentDisposable. void (^addObserverToValue)(NSObject *) = ^(NSObject *value) { RACDisposable *observerDisposable = [value rac_observeKeyPath:keyPathTail options:(options & ~NSKeyValueObservingOptionInitial) observer:weakObserver block:block]; [firstComponentDisposable() addDisposable:observerDisposable]; };
并且把keyPathTail 作為keypatch傳進(jìn)去了,就是遞歸調(diào)用,每一次進(jìn)來都會(huì)切掉第一個(gè)元素,直到BOOL keyPathHasOneComponent 這個(gè)值等于yes。從這個(gè)角度看用RACObserve(self , timeLabel.text) 這種寫法會(huì)引發(fā)遞歸調(diào)用,性能不如RACObserve(self.timeLabel.text)。
2.集合操作
假設(shè)現(xiàn)在有一個(gè)需求,有一串密碼的數(shù)組,我們判斷密碼長度小于6位就是太短,就會(huì)系統(tǒng)內(nèi)部拋出一個(gè)消息:XXX密碼太短不合格。采用RAC的寫法會(huì)比常規(guī)寫法方便,一個(gè)過濾一個(gè)自定義然后直接返回。
NSArray *pwds = @[@"",@"",@"",@""]; RACSequence *results = [[pwds.rac_sequence filter:^ BOOL (NSString *pwd) { return pwd.length < ; }]map:^id(NSString *pwd) { return [[pwd mutableCopy]stringByAppendingString:@"密碼太短不合格"]; }]; NSLog(@"%@",results.array);
中間filter方法的block內(nèi)代碼會(huì)在下面results.array代碼執(zhí)行時(shí)才會(huì)執(zhí)行, 相當(dāng)于是有了個(gè)訂閱者才會(huì)執(zhí)行。這一點(diǎn)和RACSignal很像,因?yàn)閟ignal 和 sequence 都是streams,他們共享很多相同的方法signal是push驅(qū)動(dòng)的stream,sequence是pull驅(qū)動(dòng)的stream。
如果相從RACSequence對(duì)象中取出其他屬性時(shí)進(jìn)行操作也可以用如下方法
RACSequence *s = [RACSequence sequenceWithHeadBlock:^id{ return @"自定義操作"; } tailBlock:^RACSequence *{ return [RACSequence new]; }]; NSLog(@"%@",s.head); NSLog(@"%@",s.tail);
兩個(gè)block分別會(huì)在指定屬性被調(diào)用時(shí)才會(huì)執(zhí)行,注意head就是sequence的第一個(gè)元素,而tail是除去第一個(gè)元素的剩余所有,所以還是一個(gè)sequence。(董鉑然博客園)
3.信號(hào)實(shí)現(xiàn)游戲技能釋放
假設(shè)現(xiàn)在需要用RAC模擬一個(gè)街機(jī)里放爆氣技能的方法。 按下了指定的按鈕順序下前下前拳就會(huì)釋放絕招。
首先需要將各個(gè)按鈕連線,并設(shè)置一個(gè)信號(hào)來監(jiān)聽所有按鍵單獨(dú)信號(hào)的并集,捕捉到每個(gè)按鈕的title。
// 把六個(gè)按鍵的信號(hào)合并 RACSignal *comboSignal = [[RACSignal merge:@[ [self.topBtn rac_signalForControlEvents:UIControlEventTouchUpInside], [self.bottomBtn rac_signalForControlEvents:UIControlEventTouchUpInside], [self.leftBtn rac_signalForControlEvents:UIControlEventTouchUpInside], [self.rightBtn rac_signalForControlEvents:UIControlEventTouchUpInside], [self.BBtn rac_signalForControlEvents:UIControlEventTouchUpInside], [self.ABtn rac_signalForControlEvents:UIControlEventTouchUpInside]]] map:^id(UIButton *btn) { return btn.currentTitle; }];
然后對(duì)這個(gè)信號(hào)源進(jìn)行buffer操作,把每三秒收到的所有按鍵信息都捕獲到,并進(jìn)行判斷和后繼操作
// 設(shè)置觸發(fā)爆氣條件 NSString *comboCode = @"下前下前拳"; // 實(shí)際操作 RACSignal *canAction = [[[comboSignal bufferWithTime: onScheduler:[RACScheduler mainThreadScheduler]] map:^id(RACTuple *value) { return [[value allObjects] componentsJoinedByString:@""]; }] map:^id(NSString *value) { return @([value containsString:comboCode]); }]; // 調(diào)用combo:方法就是技能釋放 [self rac_liftSelector:@selector(combo:) withSignalsFromArray:@[canAction]];
上面的代碼可以實(shí)現(xiàn)預(yù)計(jì)的功能,只要你能在三秒的buffer內(nèi)按出指定的按鍵就能釋放。但是用這個(gè)方法中間也有一個(gè)問題:設(shè)置了buffer3秒后這個(gè)block里面每隔三秒才會(huì)來到一次,也就是說如果你在0.5秒內(nèi)就按出了技能,那也需要再等2.5秒才能放出技能,顯然這個(gè)在實(shí)戰(zhàn)中是不能接受的。
于是嘗試了其他的實(shí)現(xiàn)思路,嘗試了takeLast:及takeUntilBlock:及scanWithStart: 等方法都不是很合適,最后使用了aggregateWithStart: 達(dá)到了需求的目的。
// 設(shè)置觸發(fā)爆氣條件 NSString *comboCode = @"下前下前拳"; // 實(shí)際操作 _time = [[[NSDate alloc] init] timeIntervalSince1970]; [[comboSignal aggregateWithStart:@"" reduce:^id(NSString* running, NSString* next) { if (([[[NSDate alloc] init] timeIntervalSince1970] - _time) < 3){ NSString *str = [NSString stringWithFormat:@"%@%@",running,next]; return [str containsString:comboCode]?[self combo]:str; } _time = [[[NSDate alloc] init] timeIntervalSince1970]; return str.length < combo.length ? str : [str subStringFromIndex:str.length - comboCode.length]; }]subscribeNext:^(id x){ }];
使用這段代碼可以在滿足之前條件的前提下,并且按鈕一按完馬上觸發(fā)技能。
aggregateWithStart:reduce:的第一個(gè)參數(shù)是初始值,第二個(gè)參數(shù)是一個(gè)block,這個(gè)block的返回值就是下一次來到這個(gè)block的 running參數(shù)。我在這個(gè)block的循環(huán)中做的操作有:
1.對(duì)時(shí)間進(jìn)行delta計(jì)算,如果距離上一次時(shí)間節(jié)點(diǎn)大于3秒,刷新時(shí)間節(jié)點(diǎn)重新計(jì)時(shí)。 str小于5則返回,大于5則截取后五位返回。
2.如果小于3秒則把每次按鍵信息聚合成一個(gè)字符串并判斷是否包含技能觸發(fā)代碼。
3.滿足的話觸發(fā)技能,技能方法的內(nèi)部也刷新了時(shí)間節(jié)點(diǎn),并截取running(保留最后4位,防止上一個(gè)循環(huán)結(jié)束和下一個(gè)循環(huán)開始所滿足的條件)。不滿足則將這個(gè)字符串繼續(xù)返回。
雖然代碼寫的不是很好看,但是功能是實(shí)現(xiàn)了,感覺有點(diǎn)別扭,因?yàn)楹瘮?shù)式編程倡導(dǎo)的是引用透明無副作用,所以上面需要記錄值和成員變量的做法很明顯就不適合用RAC了,應(yīng)該還會(huì)有更好的方法實(shí)現(xiàn)。
4.其他RAC操作
1)映射:flattenMap,Map用于把源信號(hào)內(nèi)容映射成新的內(nèi)容
2)組合:concat:按一定順序拼接信號(hào),當(dāng)多個(gè)信號(hào)發(fā)出的時(shí)候,有順序的接收信號(hào)
3)`then`:用于連接兩個(gè)信號(hào),當(dāng)?shù)谝粋€(gè)信號(hào)完成,才會(huì)連接then返回的信號(hào)
4)`merge`:把多個(gè)信號(hào)合并為一個(gè)信號(hào),任何一個(gè)信號(hào)有新值的時(shí)候就會(huì)調(diào)用
5)`combineLatest`:將多個(gè)信號(hào)合并起來,并且拿到各個(gè)信號(hào)的最新的值,必須每個(gè)合并的signal至少都有過一次sendNext,才會(huì)觸發(fā)合并的信號(hào)。
6)`reduce`聚合:用于信號(hào)發(fā)出的內(nèi)容是元組,把信號(hào)發(fā)出元組的值聚合成一個(gè)值
7)filter:過濾信號(hào),使用它可以獲取滿足條件的信號(hào).
8) ignore:忽略完某些值的信號(hào).
9) distinctUntilChanged:當(dāng)上一次的值和當(dāng)前的值有明顯的變化就會(huì)發(fā)出信號(hào),否則會(huì)被忽略掉
10) take:從開始一共取N次的信號(hào)
11)takeLast:取最后N次的信號(hào),前提條件,訂閱者必須調(diào)用完成,因?yàn)橹挥型瓿?,就知道總共有多少信?hào)
12)takeUntil:(RACSignal *):獲取信號(hào)直到某個(gè)信號(hào)執(zhí)行完成
13)skip:(NSUInteger):跳過幾個(gè)信號(hào),不接受
14)switchToLatest:用于signalOfSignals(信號(hào)的信號(hào)),有時(shí)候信號(hào)也會(huì)發(fā)出信號(hào),會(huì)在signalOfSignals中,獲取signalOfSignals發(fā)送的最新信號(hào)
15)doNext: 執(zhí)行Next之前,會(huì)先執(zhí)行這個(gè)Block
16)doCompleted: 執(zhí)行sendCompleted之前,會(huì)先執(zhí)行這個(gè)Block
17)deliverOn: 內(nèi)容傳遞切換到制定線程中,副作用在原來線程中,把在創(chuàng)建信號(hào)時(shí)block中的代碼稱之為副作用
18)subscribeOn: 內(nèi)容傳遞和副作用都會(huì)切換到制定線程中
19)interval 定時(shí):每隔一段時(shí)間發(fā)出信號(hào)
20)delay 延遲發(fā)送next。
21) 代替代理:
•rac_signalForSelector:用于替代代理。
22) 代替KVO :
•rac_valuesAndChangesForKeyPath:用于監(jiān)聽某個(gè)對(duì)象的屬性改變。
23) 監(jiān)聽事件:
•rac_signalForControlEvents:用于監(jiān)聽某個(gè)事件。
24) 代替通知:
•rac_addObserverForName:用于監(jiān)聽某個(gè)通知。
25) 監(jiān)聽文本框文字改變:
•rac_textSignal:只要文本框發(fā)出改變就會(huì)發(fā)出這個(gè)信號(hào)。
26) 處理當(dāng)界面有多次請(qǐng)求時(shí),需要都獲取到數(shù)據(jù)時(shí),才能展示界面
•rac_liftSelector:withSignalsFromArray:Signals:當(dāng)傳入的Signals(信號(hào)數(shù)組),每一個(gè)signal都至少sendNext過一次,就會(huì)去觸發(fā)第一個(gè)selector參數(shù)的方法。
•使用注意:幾個(gè)信號(hào),參數(shù)一的方法就幾個(gè)參數(shù),每個(gè)參數(shù)對(duì)應(yīng)信號(hào)發(fā)出的數(shù)據(jù)
RAC曾經(jīng)被冠以 學(xué)習(xí)成本搞,可讀性差,debug的噩夢(mèng)等不良評(píng)價(jià),但隨著近幾年的演變已逐漸被企業(yè)級(jí)項(xiàng)目所接受,并且成為函數(shù)響應(yīng)式編程主流框架。RAC用人越來越多,隨筆和博客也越來越多,學(xué)習(xí)的門檻已經(jīng)大大降低。 并且我覺得初學(xué)者沒有必要一開始就把所有操作和概念都弄懂,可以從簡單的用法開始一步步的接觸高階語法,這樣會(huì)更容易接受。
以上所述是小編給大家介紹的ReactiveCocoa代碼實(shí)踐之-更多思考,希望對(duì)大家有所幫助!
相關(guān)文章
利用Android畫圓弧canvas.drawArc()實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于利用Android畫圓弧canvas.drawArc()的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的理解和學(xué)習(xí)具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11Android中實(shí)現(xiàn)多行、水平滾動(dòng)的分頁的Gridview實(shí)例源碼
如果單行水平滾動(dòng),可以用Horizontalscrollview實(shí)現(xiàn)。如果是多行水平滾動(dòng),則結(jié)合Gridview(一般是垂直滾動(dòng)的)和Horizontalscrollview實(shí)現(xiàn)2013-06-06仿網(wǎng)易新聞客戶端頭條ViewPager嵌套實(shí)例
正確使用requestDisallowInterceptTouchEvent(boolean flag)方法,下面為大家介紹下外層ViewPager布局的實(shí)例,感興趣的朋友可以參考下哈2013-06-06基于TransactionTooLargeException異常分析
下面小編就為大家分享一篇基于TransactionTooLargeException異常分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-11-11Android DrawerLayout帶有側(cè)滑功能的布局類(1)
這篇文章主要為大家詳細(xì)介紹了Android DrawerLayout帶有側(cè)滑功能的布局類,感興趣的小伙伴們可以參考一下2016-07-07