日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ReactiveCocoa入门

發布時間:2024/7/23 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ReactiveCocoa入门 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

概述

為什么要使用RAC? 一個怪怪的東西,從Demo看也沒有讓代碼變得更好、更短,相反還造成理解上的困難,真的有必要去學它么?相信這是大多數人在接觸RAC時的想法。RAC不是單一功能的模塊,它是一個Framework,提供了一整套解決方案。其核心思想是「響應數據的變化」,在這個基礎上有了Signal的概念,進而可以幫助減少狀態變量(可以參考jspahrsummers的PPT),使用MVVM架構,統一的異步編程模型等等。 為什么RAC更加適合編寫Cocoa App?說這個之前,我們先來看下Web前端編程,因為有些相似之處。目前很火的AngularJS有一個很重要的特性:數據與視圖綁定。就是當數據變化時,視圖不需要額外的處理,便可正確地呈現最新的數據。而這也是RAC的亮點之一。RAC與Cocoa的編程模式,有點像AngularJS和jQuery。所以要了解RAC,需要先在觀念上做調整。 以下面這個Cell為例 正常的寫法可能是這樣,很直觀。
  • -?(void)configureWithItem:(HBItem?*)item?
  • {?
  • ????self.username.text?=?item.text;?
  • ????[self.avatarImageView?setImageWithURL:?item.avatarURL];?
  • ????//?其他的一些設置?
  • }?
  • 但如果用RAC,可能就是這樣
  • -?(id)init?
  • {?
  • ????if?(self?=?[super?init])?{?
  • ????????@weakify(self);?
  • ????????[RACObserve(self,?viewModel)?subscribeNext:^(HBItemViewModel?*viewModel)?{?
  • ????????????@strongify(self);?
  • ????????????self.username.text?=?viewModel.item.text;?
  • ????????????[self.avatarImageView?setImageWithURL:?viewModel.item.avatarURL];?
  • ????????????//?其他的一些設置?
  • ????????}];?
  • ????}?
  • }?
  • 也就是先把數據綁定,接下來只要數據有變化,就會自動響應變化。在這里,每次viewModel改變時,內容就會自動變成該viewModel的內容。 Signal Signal是RAC的核心,為了幫助理解,畫了這張簡化圖 這里的數據源和sendXXX,可以理解為函數的參數和返回值。當Signal處理完數據后,可以向下一個Signal或Subscriber傳送數據。可以看到上半部分的兩個Signal是冷的(cold),相當于實現了某個函數,但該函數沒有被調用。同時也說明了Signal可以被組合使用,比如RACSignal *signalB = [signalA map:^id(id x){return x}],或RACSignal *signalB = [signalA take:1]等等。 當signal被subscribe時,就會處于熱(hot)的狀態,也就是該函數會被執行。比如上面的第二張圖,首先signalA可能發了一個網絡請求,拿到結果后,把數據通過sendNext方法傳遞到下一個signal,signalB可以根據需要做進一步處理,比如轉換成相應的Model,轉換完后再sendNext到subscriber,subscriber拿到數據后,再改變ViewModel,同時因為View已經綁定了ViewModel,所以拿到的數據會自動在View里呈現。 還有,一個signal可以被多個subscriber訂閱,這里怕顯得太亂就沒有畫出來,但每次被新的subscriber訂閱時,都會導致數據源的處理邏輯被觸發一次,這很有可能導致意想不到的結果,需要注意一下。 當數據從signal傳送到subscriber時,還可以通過doXXX來做點事情,比如打印數據。 通過這張圖可以看到,這非常像中學時學的函數,比如 f(x) = y,某一個函數的輸出又可以作為另一個函數的輸入,比如 f(f(x)) = z,這也正是「函數響應式編程」(FRP)的核心。 有些地方需要注意下,比如把signal作為local變量時,如果沒有被subscribe,那么方法執行完后,該變量會被dealloc。但如果signal有被subscribe,那么subscriber會持有該signal,直到signal sendCompleted或sendError時,才會解除持有關系,signal才會被dealloc。 RACCommand RACCommand是RAC很重要的組成部分,可以節省很多時間并且讓你的App變得更Robust,這篇文章可以幫助你更深入的理解,這里簡單做一下介紹。 RACCommand 通常用來表示某個Action的執行,比如點擊Button。它有幾個比較重要的屬性:executionSignals / errors / executing。 1、executionSignals是signal of signals,如果直接subscribe的話會得到一個signal,而不是我們想要的value,所以一般會配合switchToLatest。 2、errors。跟正常的signal不一樣,RACCommand的錯誤不是通過sendError來實現的,而是通過errors屬性傳遞出來的。 3、executing表示該command當前是否正在執行。 假設有這么個需求:當圖片載入完后,分享按鈕才可用。那么可以這樣:
  • RACSignal?*imageAvailableSignal?=?[RACObserve(self,?imageView.image)?map:id^(id?x){return?x???@YES?:?@NO}];?
  • self.shareButton.rac_command?=?[[RACCommand?alloc]?initWithEnabled:imageAvailableSignal?signalBlock:^RACSignal?*(id?input)?{?
  • ????//?do?share?logic?
  • }];?
  • 除了與UIControl綁定之外,也可以手動執行某個command,比如雙擊圖片點贊,就可以這么實現。
  • //?ViewModel.m?
  • -?(instancetype)init?
  • {?
  • ????self?=?[super?init];?
  • ????if?(self)?{?
  • ????????void?(^updatePinLikeStatus)()?=?^{?
  • ????????????self.pin.likedCount?=?self.pin.hasLiked???self.pin.likedCount?-?1?:?self.pin.likedCount?+?1;?
  • ????????????self.pin.hasLiked?=?!self.pin.hasLiked;?
  • ????????};?
  • ?????????
  • ????????_likeCommand?=?[[RACCommand?alloc]?initWithSignalBlock:^RACSignal?*(id?input)?{?
  • ????????????//?先展示效果,再發送請求?
  • ????????????updatePinLikeStatus();?
  • ????????????return?[[HBAPIManager?sharedManager]?likePinWithPinID:self.pin.pinID];?
  • ????????}];?
  • ?????????
  • ????????[_likeCommand.errors?subscribeNext:^(id?x)?{?
  • ????????????//?發生錯誤時,回滾?
  • ????????????updatePinLikeStatus();?
  • ????????}];?
  • ????}?
  • ????return?self;?
  • }?
  • ?
  • //?ViewController.m?
  • -?(void)viewDidLoad?
  • {?
  • ????[super?viewDidLoad];?
  • ????//?...?
  • ????@weakify(self);?
  • ????[RACObserve(self,?viewModel.hasLiked)?subscribeNex:^(id?x){?
  • ????????@strongify(self);?
  • ????????self.pinLikedCountLabel.text?=?self.viewModel.likedCount;?
  • ????????self.likePinImageView.image?=?[UIImage?imageNamed:self.viewModel.hasLiked???@"pin_liked"?:?@"pin_like"];?
  • ????}];?
  • ?????
  • ????UITapGestureRecognizer?*tapGesture?=?[[UITapGestureRecognizer?alloc]?init];?
  • ????tapGesture.numberOfTapsRequired?=?2;?
  • ????[[tapGesture?rac_gestureSignal]?subscribeNext:^(id?x)?{?
  • ????????[self.viewModel.likeCommand?execute:nil];?
  • ????}];?
  • }?
  • 再比如某個App要通過Twitter登錄,同時允許取消登錄,就可以這么做 (source)
  • _twitterLoginCommand?=?[[RACCommand?alloc]?initWithSignalBlock:^(id?_)?{?
  • ??????@strongify(self);?
  • ??????return?[[self??
  • ??????????twitterSignInSignal]??
  • ??????????takeUntil:self.cancelCommand.executionSignals];?
  • ????}];?
  • ?
  • RAC(self.authenticatedUser)?=?[self.twitterLoginCommand.executionSignals?switchToLatest];?
  • 常用的模式 map + switchToLatest switchToLatest: 的作用是自動切換signal of signals到最后一個,比如之前的command.executionSignals就可以使用switchToLatest:。 map:的作用很簡單,對sendNext的value做一下處理,返回一個新的值。 如果把這兩個結合起來就有意思了,想象這么個場景,當用戶在搜索框輸入文字時,需要通過網絡請求返回相應的hints,每當文字有變動時,需要取消上一次的請求,就可以使用這個配搭。這里用另一個Demo,簡單演示一下
  • NSArray?*pins?=?@[@172230988,?@172230947,?@172230899,?@172230777,?@172230707];?
  • __block?NSInteger?index?=?0;?
  • ?
  • RACSignal?*signal?=?[[[[RACSignal?interval:0.1?onScheduler:[RACScheduler?scheduler]]?
  • ????????????????????????take:pins.count]?
  • ????????????????????????map:^id(id?value)?{?
  • ????????????????????????????return?[[[HBAPIManager?sharedManager]?fetchPinWithPinID:[pins[index++]?intValue]]?doNext:^(id?x)?{?
  • ????????????????????????????????NSLog(@"這里只會執行一次");?
  • ????????????????????????????}];?
  • ????????????????????????}]?
  • ????????????????????????switchToLatest];?
  • ?
  • [signal?subscribeNext:^(HBPin?*pin)?{?
  • ????NSLog(@"pinID:%d",?pin.pinID);?
  • }?completed:^{?
  • ????NSLog(@"completed");?
  • }];?
  • ?
  • //?output?
  • //?2014-06-05?17:40:49.851?這里只會執行一次?
  • //?2014-06-05?17:40:49.851?pinID:172230707?
  • //?2014-06-05?17:40:49.851?completed?
  • takeUntil takeUntil:someSignal 的作用是當someSignal sendNext時,當前的signal就sendCompleted,someSignal就像一個拳擊裁判,哨聲響起就意味著比賽終止。 它的常用場景之一是處理cell的button的點擊事件,比如點擊Cell的詳情按鈕,需要push一個VC,就可以這樣:
  • [[[cell.detailButton?
  • ????rac_signalForControlEvents:UIControlEventTouchUpInside]?
  • ????takeUntil:cell.rac_prepareForReuseSignal]?
  • ????subscribeNext:^(id?x)?{?
  • ????????//?generate?and?push?ViewController?
  • }];?
  • 如果不加takeUntil:cell.rac_prepareForReuseSignal,那么每次Cell被重用時,該button都會被addTarget:selector。 替換Delegate 出現這種需求,通常是因為需要對Delegate的多個方法做統一的處理,這時就可以造一個signal出來,每次該Delegate的某些方法被觸發時,該signal就會sendNext。
  • @implementation?UISearchDisplayController?(RAC)?
  • -?(RACSignal?*)rac_isActiveSignal?{?
  • ????self.delegate?=?self;?
  • ????RACSignal?*signal?=?objc_getAssociatedObject(self,?_cmd);?
  • ????if?(signal?!=?nil)?return?signal;?
  • ?????
  • ????/*?Create?two?signals?and?merge?them?*/?
  • ????RACSignal?*didBeginEditing?=?[[self?rac_signalForSelector:@selector(searchDisplayControllerDidBeginSearch:)??
  • ????????????????????????????????????????fromProtocol:@protocol(UISearchDisplayDelegate)]?mapReplace:@YES];?
  • ????RACSignal?*didEndEditing?=?[[self?rac_signalForSelector:@selector(searchDisplayControllerDidEndSearch:)??
  • ??????????????????????????????????????fromProtocol:@protocol(UISearchDisplayDelegate)]?mapReplace:@NO];?
  • ????signal?=?[RACSignal?merge:@[didBeginEditing,?didEndEditing]];?
  • ?????
  • ?????
  • ????objc_setAssociatedObject(self,?_cmd,?signal,?OBJC_ASSOCIATION_RETAIN_NONATOMIC);?
  • ????return?signal;?
  • }?
  • @end?
  • 代碼源于此文 使用ReactiveViewModel的didBecomActiveSignal ReactiveViewModel是另一個project, 后面的MVVM中會講到,通常的做法是在VC里設置VM的active屬性(RVMViewModel自帶該屬性),然后在VM里subscribeNext didBecomActiveSignal,比如當Active時,獲取TableView的最新數據。 RACSubject的使用場景 一般不推薦使用RACSubject,因為它過于靈活,濫用的話容易導致復雜度的增加。但有一些場景用一下還是比較方便的,比如ViewModel的errors。 ViewModel一般會有多個RACCommand,那這些commands如果出現error了該如何處理呢?比較方便的方法如下:
  • //?HBCViewModel.h?
  • ?
  • #import?"RVMViewModel.h"?
  • ?
  • @class?RACSubject;?
  • ?
  • @interface?HBCViewModel?:?RVMViewModel?
  • @property?(nonatomic)?RACSubject?*errors;?
  • @end?
  • ?
  • ?
  • ?
  • //?HBCViewModel.m?
  • ?
  • #import?"HBCViewModel.h"?
  • #import?<ReactiveCocoa.h>?
  • ?
  • @implementation?HBCViewModel?
  • ?
  • -?(instancetype)init?
  • {?
  • ????self?=?[super?init];?
  • ????if?(self)?{?
  • ????????_errors?=?[RACSubject?subject];?
  • ????}?
  • ????return?self;?
  • }?
  • ?
  • -?(void)dealloc?
  • {?
  • ????[_errors?sendCompleted];?
  • }?
  • @end?
  • ?
  • //?Some?Other?ViewModel?inherit?HBCViewModel?
  • ?
  • -?(instancetype)init?
  • {?
  • ????_fetchLatestCommand?=?[RACCommand?alloc]?initWithSignalBlock:^RACSignal?*(id?input){?
  • ????????//?fetch?latest?data?
  • ????}];?
  • ?
  • ????_fetchMoreCommand?=?[RACCommand?alloc]?initWithSignalBlock:^RACSignal?*(id?input){?
  • ????????//?fetch?more?data?
  • ????}];?
  • ?
  • ????[self.didBecomeActiveSignal?subscribeNext:^(id?x)?{?
  • ????????[_fetchLatestCommand?execute:nil];?
  • ????}];?
  • ?????
  • ????[[RACSignal?
  • ????????merge:@[?
  • ????????????????_fetchMoreCommand.errors,?
  • ????????????????_fetchLatestCommand.errors?
  • ????????????????]]?subscribe:self.errors];?
  • ?
  • }?
  • rac_signalForSelector rac_signalForSelector: 這個方法會返回一個signal,當selector執行完時,會sendNext,也就是當某個方法調用完后再額外做一些事情。用在category會比較方便,因為Category重寫父類的方法時,不能再通過[super XXX]來調用父類的方法,當然也可以手寫Swizzle來實現,不過有了rac_signalForSelector:就方便多了。 rac_signalForSelector: fromProtocol: 可以直接實現對protocol的某個方法的實現(聽著有點別扭呢),比如,我們想實現UIScrollViewDelegate的某些方法,可以這么寫
  • [[self?rac_signalForSelector:@selector(scrollViewDidEndDecelerating:)?fromProtocol:@protocol(UIScrollViewDelegate)]?subscribeNext:^(RACTuple?*tuple)?{?
  • ????//?do?something?
  • }];?
  • ?
  • [[self?rac_signalForSelector:@selector(scrollViewDidScroll:)?fromProtocol:@protocol(UIScrollViewDelegate)]?subscribeNext:^(RACTuple?*tuple)?{?
  • ????//?do?something?
  • }];?
  • ?
  • self.scrollView.delegate?=?nil;?
  • self.scrollView.delegate?=?self;?
  • 注意,這里的delegate需要先設置為nil,再設置為self,而不能直接設置為self,如果self已經是該scrollView的Delegate的話。 有時,我們想對selector的返回值做一些處理,但很遺憾RAC不支持,如果真的有需要的話,可以使用Aspects MVVM 這是一個大話題,如果有耐心,且英文還不錯的話,可以看一下Cocoa Samurai的這兩篇文章。PS: Facebook Paper就是基于MVVM構建的。 MVVM是Model-View-ViewModel的簡稱,它們之間的關系如下 可以看到View(其實是ViewController)持有ViewModel,這樣做的好處是ViewModel更加獨立且可測試,ViewModel里不應包含任何View相關的元素,哪怕換了一個View也能正常工作。而且這樣也能讓View/ViewController「瘦」下來。 ViewModel主要做的事情是作為View的數據源,所以通常會包含網絡請求。 或許你會疑惑,ViewController哪去了?在MVVM的世界里,ViewController已經成為了View的一部分。它的主要職責是將VM與View綁定、響應VM數據的變化、調用VM的某個方法、與其他的VC打交道。 而RAC為MVVM帶來很大的便利,比如RACCommand, UIKit的RAC Extension等等。使用MVVM不一定能減少代碼量,但能降低代碼的復雜度。 以下面這個需求為例,要求大圖滑動結束時,底部的縮略圖滾動到對應的位置,并高亮該縮略圖;同時底部的縮略圖被選中時,大圖也要變成該縮略圖的大圖。 我的思路是橫向滾動的大圖是一個collectionView,該collectionView是當前頁面VC的一個property。底部可以滑動的縮略圖是一個childVC的collectionView,這兩個collectionView共用一套VM,并且各自RACObserve感興趣的property。 比如大圖滑到下一頁時,會改變VM的indexPath屬性,而底部的collectionView所在的VC正好對該indexPath感興趣,只要indexPath變化就滾動到相應的Item
  • //?childVC?
  • ?
  • -?(void)viewDidLoad?
  • {?
  • ????[super?viewDidLoad];?
  • ?
  • ????@weakify(self);?
  • ????[RACObserve(self,?viewModel.indexPath)?subscribeNext:^(NSNumber?*index)?{?
  • ????????@strongify(self);?
  • ????????[self?scrollToIndexPath];?
  • ????}];?
  • }?
  • ?
  • -?(void)scrollToIndexPath?
  • {?
  • ????if?(self.collectionView.subviews.count)?{?
  • ????????NSIndexPath?*indexPath?=?self.viewModel.indexPath;?
  • ????????[self.collectionView?scrollToItemAtIndexPath:indexPath?atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally?animated:YES];?
  • ????????[self.collectionView.subviews?enumerateObjectsUsingBlock:^(UIView?*view,?NSUInteger?idx,?BOOL?*stop)?{?
  • ????????????view.layer.borderWidth?=?0;?
  • ????????}];?
  • ????????UIView?*view?=?[self.collectionView?cellForItemAtIndexPath:indexPath];?
  • ????????view.layer.borderWidth?=?kHBPinsNaviThumbnailPadding;?
  • ????????view.layer.borderColor?=?[UIColor?whiteColor].CGColor;?
  • ????}?
  • }?
  • 當點擊底部的縮略圖時,上面的大圖也要做出變化,也同樣可以通過RACObserve indexPath來實現
  • //?PinsViewController.m?
  • -?(void)viewDidLoad?
  • {?
  • ????[super?viewDidLoad];?
  • ????@weakify(self);?
  • ????[[RACObserve(self,?viewModel.indexPath)?
  • ????????skip:1]?
  • ????????subscribeNext:^(NSIndexPath?*indexPath)?{?
  • ????????????@strongify(self);?
  • ????????????[self.collectionView?scrollToItemAtIndexPath:indexPath?atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally?animated:YES];?
  • ????}];?
  • }?
  • 這里有一個小技巧,當Cell里的元素比較復雜時,我們可以給Cell也準備一個ViewModel,這個CellViewModel可以由上一層的ViewModel提供,這樣Cell如果需要相應的數據,直接跟CellViewModel要即可,CellViewModel也可以包含一些command,比如likeCommand。假如點擊Cell時,要做一些處理,也很方便。
  • //?CellViewModel已經在ViewModel里準備好了?
  • -?(UICollectionViewCell?*)collectionView:(UICollectionView?*)collectionView?cellForItemAtIndexPath:(NSIndexPath?*)indexPath?
  • {?
  • ????HBPinsCell?*cell?=?[collectionView?dequeueReusableCellWithReuseIdentifier:cellIdentifier?forIndexPath:indexPath];?
  • ????cell.viewModel?=?self.viewModel.cellViewModels[indexPath.row];?
  • ????return?cell;?
  • }?
  • ?
  • -?(void)collectionView:(UICollectionView?*)collectionView?didSelectItemAtIndexPath:(NSIndexPath?*)indexPath?
  • {?
  • ????HBCellViewModel?*cellViewModel?=?self.viewModel.cellViewModels[indexPath.row];?
  • ????//?對cellViewModel執行某些操作,因為Cell已經與cellViewModel綁定,所以cellViewModel的改變也會反映到Cell上?
  • ????//?或拿到cellViewModel的數據來執行某些操作?
  • }?
  • ViewModel中signal, property, command的使用 初次使用RAC+MVVM時,往往會疑惑,什么時候用signal,什么時候用property,什么時候用command? 一般來說可以使用property的就直接使用,沒必要再轉換成signal,外部RACObserve即可。使用signal的場景一般是涉及到多個property或多個signal合并為一個signal。command往往與UIControl/網絡請求掛鉤。 常見場景的處理 檢查本地緩存,如果失效則去請求網絡數據并緩存到本地 來源
  • -?(RACSignal?*)loadData?{?
  • ????return?[[RACSignal??
  • ????????createSignal:^(id<RACSubscriber>?subscriber)?{?
  • ????????????//?If?the?cache?is?valid?then?we?can?just?immediately?send?the??
  • ????????????//?cached?data?and?be?done.?
  • ????????????if?(self.cacheValid)?{?
  • ????????????????[subscriber?sendNext:self.cachedData];?
  • ????????????????[subscriber?sendCompleted];?
  • ????????????}?else?{?
  • ????????????????[subscriber?sendError:self.staleCacheError];?
  • ????????????}?
  • ????????}]??
  • ????????//?Do?the?subscription?work?on?some?random?scheduler,?off?the?main??
  • ????????//?thread.?
  • ????????subscribeOn:[RACScheduler?scheduler]];?
  • }?
  • ?
  • -?(void)update?{?
  • ????[[[[self??
  • ????????loadData]?
  • ????????//?Catch?the?error?from?-loadData.?It?means?our?cache?is?stale.?Update?
  • ????????//?our?cache?and?save?it.?
  • ????????catch:^(NSError?*error)?{?
  • ????????????return?[[self?updateCachedData]?doNext:^(id?data)?{?
  • ????????????????[self?cacheData:data];?
  • ????????????}];?
  • ????????}]??
  • ????????//?Our?work?up?until?now?has?been?on?a?background?scheduler.?Get?our??
  • ????????//?results?delivered?on?the?main?thread?so?we?can?do?UI?work.?
  • ????????deliverOn:RACScheduler.mainThreadScheduler]?
  • ????????subscribeNext:^(id?data)?{?
  • ????????????//?Update?your?UI?based?on?`data`.?
  • ?
  • ????????????//?Update?again?after?`updateInterval`?seconds?have?passed.?
  • ????????????[[RACSignal?interval:updateInterval]?take:1]?subscribeNext:^(id?_)?{?
  • ????????????????[self?update];?
  • ????????????}];?
  • ????????}];??
  • }?
  • 檢測用戶名是否可用 ? 來源
  • -?(void)setupUsernameAvailabilityChecking?{?
  • ????RAC(self,?availabilityStatus)?=?[[[RACObserve(self.userTemplate,?username)?
  • ??????????????????????????????????????throttle:kUsernameCheckThrottleInterval]?//throttle表示interval時間內如果有sendNext,則放棄該nextValue?
  • ??????????????????????????????????????map:^(NSString?*username)?{?
  • ??????????????????????????????????????????if?(username.length?==?0)?return?[RACSignal?return:@(UsernameAvailabilityCheckStatusEmpty)];?
  • ??????????????????????????????????????????return?[[[[[FIBAPIClient?sharedInstance]?
  • ????????????????????????????????????????????????getUsernameAvailabilityFor:username?ignoreCache:NO]?
  • ??????????????????????????????????????????????map:^(NSDictionary?*result)?{?
  • ??????????????????????????????????????????????????NSNumber?*existsNumber?=?result[@"exists"];?
  • ??????????????????????????????????????????????????if?(!existsNumber)?return?@(UsernameAvailabilityCheckStatusFailed);?
  • ??????????????????????????????????????????????????UsernameAvailabilityCheckStatus?status?=?[existsNumber?boolValue]???UsernameAvailabilityCheckStatusUnavailable?:?UsernameAvailabilityCheckStatusAvailable;?
  • ??????????????????????????????????????????????????return?@(status);?
  • ??????????????????????????????????????????????}]?
  • ?????????????????????????????????????????????catch:^(NSError?*error)?{?
  • ??????????????????????????????????????????????????return?[RACSignal?return:@(UsernameAvailabilityCheckStatusFailed)];?
  • ??????????????????????????????????????????????}]?startWith:@(UsernameAvailabilityCheckStatusChecking)];?
  • ??????????????????????????????????????}]?
  • ??????????????????????????????????????switchToLatest];?
  • }?
  • 可以看到這里也使用了map + switchToLatest模式,這樣就可以自動取消上一次的網絡請求。 startWith的內部實現是concat,這里表示先將狀態置為checking,然后再根據網絡請求的結果設置狀態。 使用takeUntil:來處理Cell的button點擊 這個上面已經提到過了。 token過期后自動獲取新的 開發APIClient時,會用到AccessToken,這個Token過一段時間會過期,需要去請求新的Token。比較好的用戶體驗是當token過期后,自動去獲取新的Token,拿到后繼續上一次的請求,這樣對用戶是透明的。
  • RACSignal?*requestSignal?=?[RACSignal?createSignal:^RACDisposable?*(id<RACSubscriber>?subscriber)?{?
  • ????????//?suppose?first?time?send?request,?access?token?is?expired?or?invalid?
  • ????????//?and?next?time?it?is?correct.?
  • ????????//?the?block?will?be?triggered?twice.?
  • ????????static?BOOL?isFirstTime?=?0;?
  • ????????NSString?*url?=?@"http://httpbin.org/ip";?
  • ????????if?(!isFirstTime)?{?
  • ????????????url?=?@"http://nonexists.com/error";?
  • ????????????isFirstTime?=?1;?
  • ????????}?
  • ????????NSLog(@"url:%@",?url);?
  • ????????[[AFHTTPRequestOperationManager?manager]?GET:url?parameters:nil?success:^(AFHTTPRequestOperation?*operation,?id?responseObject)?{?
  • ????????????[subscriber?sendNext:responseObject];?
  • ????????????[subscriber?sendCompleted];?
  • ????????}?failure:^(AFHTTPRequestOperation?*operation,?NSError?*error)?{?
  • ????????????[subscriber?sendError:error];?
  • ????????}];?
  • ????????return?nil;?
  • ????}];?
  • ?????
  • ????self.statusLabel.text?=?@"sending?request...";?
  • ????[[requestSignal?catch:^RACSignal?*(NSError?*error)?{?
  • ????????self.statusLabel.text?=?@"oops,?invalid?access?token";?
  • ?????????
  • ????????//?simulate?network?request,?and?we?fetch?the?right?access?token?
  • ????????return?[[RACSignal?createSignal:^RACDisposable?*(id<RACSubscriber>?subscriber)?{?
  • ????????????double?delayInSeconds?=?1.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:@YES];?
  • ????????????????[subscriber?sendCompleted];?
  • ????????????});?
  • ????????????return?nil;?
  • ????????}]?concat:requestSignal];?
  • ????}]?subscribeNext:^(id?x)?{?
  • ????????if?([x?isKindOfClass:[NSDictionary?class]])?{?
  • ????????????self.statusLabel.text?=?[NSString?stringWithFormat:@"result:%@",?x[@"origin"]];?
  • ????????}?
  • ????}?completed:^{?
  • ????????NSLog(@"completed");?
  • ????}];?
  • 注意事項 RAC我自己感覺遇到的幾個難點是: 1) 理解RAC的理念。 2) 熟悉常用的API。3) 針對某些特定的場景,想出比較合理的RAC處理方式。不過看多了,寫多了,想多了就會慢慢適應。下面是我在實踐過程中遇到的一些小坑。 ReactiveCocoaLayout 有時Cell的內容涉及到動態的高度,就會想到用Autolayout來布局,但RAC已經為我們準備好了ReactiveCocoaLayout,所以我想不妨就拿來用一下。 ReactiveCocoaLayout的使用好比「批地」和「蓋房」,先通過insetWidth:height:nullRect從某個View中劃出一小塊,拿到之后還可以通過divideWithAmount:padding:fromEdge 再分成兩塊,或sliceWithAmount:fromEdge再分出一塊。這些方法返回的都是signal,所以可以通過RAC(self.view, frame) = someRectSignal 這樣來實現綁定。但在實踐中發現性能不是很好,多批了幾塊地就容易造成主線程卡頓。 所以ReactiveCocoaLayout最好不用或少用。 調試 剛開始寫RAC時,往往會遇到這種情況,滿屏的調用棧信息都是RAC的,要找出真正出現問題的地方不容易。曾經有一次在使用[RACSignal combineLatest: reduce:^id{}]時,忘了在Block里返回value,而Xcode也沒有提示warning,然后就是莫名其妙地掛起了,跳到了匯編上,也沒有調用棧信息,這時就只能通過最古老的注釋代碼的方式來找到問題的根源。 不過寫多了之后,一般不太會犯這種低級錯誤。 strongify / weakify dance 因為RAC很多操作都是在Block中完成的,這塊最常見的問題就是在block直接把self拿來用,造成block和self的retain cycle。所以需要通過@strongify和@weakify來消除循環引用。 有些地方很容易被忽略,比如RACObserve(thing, keypath),看上去并沒有引用self,所以在subscribeNext時就忘記了weakify/strongify。但事實上RACObserve總是會引用self,即使target不是self,所以只要有RACObserve的地方都要使用weakify/strongify。

    轉自:http://www.cocoachina.com/industry/20140609/8737.html

    總結

    以上是生活随笔為你收集整理的ReactiveCocoa入门的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。