iOS函數(shù)響應(yīng)式編程以及ReactiveCocoa的使用

打算在項目中大面積使用RAC來開發(fā)毒费,所以整理一些常用的實踐范例和比較完整的api說明方便開發(fā)時隨時查閱

聲明式編程泛型Declarative programming

函數(shù)反應(yīng)式編程是聲明式編程的子編程范式之一

高階函數(shù)

需要滿足兩個條件

  • 一個或者多個函數(shù)作為輸入。
  • 有且僅有一個函數(shù)輸出蚀苛。

Objective-c里使用block作為函數(shù)

[array enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, BOOL *stop)
{
    NSLog(@"%@",number);
}];

映射map

NSArray * mappedArray = [array rx_mapWithBlock:^id(id each){
    return @(pow([each integerValue],2));
}];

過濾filter

NSArray *filteredArray = [array rx_filterWithBlock:^BOOL(id each){
    return ([each integerValue] % 2 == 0);
}]

折疊fold

[[array rx_mapWithBlock:^id (id each){
        return [each stringValue];
    }] rx_foldInitialValue:@"" block:^id (id memo , id each){
        return [memo stringByAppendingString:each];
}];

Currying

用函數(shù)生成另一個函數(shù)

func filterGenerator(lastnameCondition: String) -> (Staff) -> (Bool) { 
    return {staff in 
        return staff.lastname == lastnameCondition
    }
}

let filterWang = filterGenerator("Wang") 
let filterHu = filterGenerator("Hu")

staffs.filter(filterHu)

RAC中使用高階函數(shù)

映射

NSArray *array = @[ @1, @2, @3 ];
RACSequence * stream = [array rac_sequence];
//RACSequence是一個RACStream的子類丙挽。
[stream map:^id (id value){
    return @(pow([value integerValue], 2));
}];
//RACSequence有一個方法返回數(shù)組:array
NSLog(@"%@",[stream array]);

//避免污染變量的作用域
NSLog(@"%@",[[[array rac_sequence] map:^id (id value){
                    return @(pow([value integerValue], 2));
                }] array]);

過濾

NSLog(@"%@", [[[array rac_sequence] filter:^BOOL (id value){
                        return [value integerValue] % 2 == 0;
                    }] array]);

折疊

NSLog(@"%@",[[[array rac_sequence] map:^id (id value){
                    return [value stringValue];
                }] foldLeftWithStart:@"" reduce:^id (id accumulator, id value){
                    return [accumulator stringByAppendingString:value];
            }]);

綁定鍵值

RACSignal * validEmailSignal = [self.textField.rac_textSignal map:^id (NSString *value){
    return @([value rangeOfString:@"@"].location != NSNotFound);
}];

RAC(self.button, enabled) = validEmailSignal;

RAC(self.textField, textColor) = [validEmailSignal map: ^id (id value){
    if([value boolValue]){
        return [UIColor greenColor];
    }else{
        return [UIColor redColor];
    }
}];
綁定鍵值圖示
綁定鍵值圖示

實踐

比較好的一個完整的RAC實踐的例子:https://github.com/ashfurrow/FunctionalReactivePixels

網(wǎng)絡(luò)請求生成對應(yīng)model

+ (RACSignal *)importPhotos{
    RACReplaySubject * subject = [RACReplaySubject subject];
    NSURLRequest * request = [self popularURLRequest];
    [NSURLConnection sendAsynchronousRequest:request
                                    queue:[NSOperationQueue mainQueue]
                        completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError){
                            if (data) {
                                id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

                                [subject sendNext:[[[results[@"photos"] rac_sequence] map:^id(NSDictionary *photoDictionary){
                                    FRPPhotoModel * model = [FRPPhotoModel new];

                                    [self configurePhotoModel:model withDictionary:photoDictionary];
                                    [self downloadThumbnailForPhotoModel:model];

                                    return model;
                                }] array]];

                                [subject sendCompleted];
                            }
                            else{
                                [subject sendError:connectionError];
                            }
    }];

    return subject;

}

過濾相同大小的圖片掖举,取出他們的url游盲,返回第一個

+ (NSString *)urlForImageSize:(NSInteger)size inDictionary:(NSArray *)array{
    return [[[[[array rac_sequence] filter:^ BOOL (NSDictionary * value){
        return [value[@"size"] integerValue] == size;
    }] map:^id (id value){
        return value[@"url"];
    }] array] firstObject];
}

觀察model里的圖片數(shù)據(jù)介袜,進(jìn)行為空過濾判斷,將data轉(zhuǎn)為UIImage唁影,再把綁定新信號的值給對象的關(guān)鍵路徑

- (void)setPhotoModel:(FRPPhotoModel *)photoModel{
    self.subscription = [[[RACObserver(photoModel, thumbnailData)
        filter:^ BOOL (id value){
            return value != nil;
        }] map:^id (id value){
            return [UIImage imageWithData:value];
        }] setKeyPath:@keypath(self.imageView, image) onObject:self.imageView];
}

UITableViewCell復(fù)用時需要取消cell上各個組件的訂閱

- (void)perpareForReuse {
    [super prepareForReuse];
    [self.subscription dispose], self.subscription = nil;
}

Delegate的使用

//注意:你必須retain這個delegate對象腿椎,否則他們將會被釋放,你將會得到一個EXC_BAD_ACCESS異常夭咬。添加下列私有屬性到畫廊視圖控制器:
@property (nonatomic, strong) id collectionViewDelegate;

//同時你也需要導(dǎo)入RACDelegateProxy.h,因為他不是ReactiveCocoa的核心部分铆隘,不包含在ReactiveCocoa.h中卓舵。
RACDelegateProxy *viewControllerDelegate = [[RACDelegateProxy alloc]
                                    initWithProtocol:@protocol(FRPFullSizePhotoViewControllerDelegate)];

[[viewControllerDelegate rac_signalForSelector:@selector(userDidScroll:toPhotoAtIndex:)     fromProtocol:@protocol(FRPFullSizePhotoViewControllerDelegate)]
        subscribeNext:^(RACTuple *value){
            @strongify(self);
            [self.collectionView
                scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:[value.second integerValue] inSection:0]
                atScrollPosition:UICollectionViewScrollPositionCenteredVertically
                animated:NO];
        }];

self.collectionViewDelegate = [[RACDelegateProxy alloc] initWithProtocol:@protocol(UICollectionViewDelegate)];

[[self.collectionViewDelegate rac_signalForSelector:@selector(collectionView:didSelectItemAtIndexPath:)]
        subscribeNext:^(RACTuple *arguments) {
            @strongify(self);
            FRPFullSizePhotoViewController *viewController = [[FRPFullSizePhotoViewController alloc] initWithPhotoModels:self.photosArray currentPhotoIndex:[(NSIndexPath *)arguments.second item]];
            viewController.delegate = (id<FRPFullSizePhotoViewControllerDelegate>)viewControllerDelegate;

            [self.navigationController pushViewController:viewController animated:YES];

        }];

處理異常,完成執(zhí)行刷新操作膀钠,異常打印日志掏湾,執(zhí)行對應(yīng)方法

RAC(self, photosArray) = [[[[FRPPhotoImporter importPhotos]
        doCompleted:^{
            @strongify(self);
            [self.collectionView reloadData];
        }] logError] catchTo:[RACSignal empty]];

網(wǎng)絡(luò)請求處理數(shù)據(jù),獲取數(shù)據(jù)返回主線程

+ (RACSignal *)importPhotos {
    NSURLRequest *request = [self popularURLRequest];

    return [[[[[[NSURLConnection rac_sendAsynchronousRequest:request]
                reduceEach:^id(NSURLResponse *response , NSData *data){
                    //注意:我們可以用下面的reduceEach:替代使用RACTuple的第一個map:肿嘲,以便提供編譯時檢查融击。
                    return data;
                }]
                deliverOn:[RACScheduler mainThreadScheduler]]
                map:^id (NSData *data) {
                    id results = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                    return [[[results[@"photo"] rac_sequence]
                        map:^id (NSDictionary *photoDictionary) {
                            FRPPhotoModel *model = [FRPPhotoModel new];
                            [self configurePhotoModel:model withDictionary:photoDictionary];
                            [self downloadThumbnailForPhotoModel:model];
                            return model;
                        }] array];
                }] publish] autoconnect];
    //信號鏈條最末端的信號操作publish. publish返回一個RACMulitcastConnection,當(dāng)信號連接上時,他將訂閱該接收信號雳窟。autoconnect為我們做的是:當(dāng)它返回的信號被訂閱尊浪,連接到 該(訂閱背后的)信號(underly signal)。
}

信號的信號Signal of signals封救,一個外部信號包含一個內(nèi)部信號拇涤,在輸出信號的subscribeNext:塊中訂閱內(nèi)部信號,會引起嵌套麻煩誉结。使用flattenMap后會生成一個新的信號鹅士,和先前信號平級,訂閱會訂閱到返回的新信號里的值惩坑。map方法也是創(chuàng)建一個新信號掉盅,但是會將返回的信號也當(dāng)做值也拜,這樣就得不到真正需要的值了。

[[[self.signInButton rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^RACStream *(id value) {
    return [self signInSignal];
}] subscribeNext:^(id x) {
    //x
    NSLog(@"Sign in result: %@", x);
}];

不同信號順序鏈接趾痘,程序需要等待前一個信號發(fā)出完成事件(sendCompleted)慢哈,然后再訂閱下一個信號(then)

- (RACSignal *)requestAccessToTwitterSignal
{
    // 定義一個錯誤,如果用戶拒絕訪問則發(fā)送
    NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain code:RWTwitterInstantErrorAccessDenied userInfo:nil];

    // 創(chuàng)建并返回信號
    @weakify(self)
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

        // 請求訪問twitter
        @strongify(self)
        [self.accountStore requestAccessToAccountsWithType:self.twitterAccountType
                                                   options:nil
                                                completion:^(BOOL granted, NSError *error) {
                                                    // 處理響應(yīng)
                                                    if (!granted)
                                                    {
                                                        [subscriber sendError:accessError];
                                                    }
                                                    else
                                                    {
                                                        [subscriber sendNext:nil];
                                                        [subscriber sendCompleted];
                                                    }
                                                }];
        return nil;
    }];
}

//throttle可以避免連續(xù)輸入造成的不必要的請求扼脐,then會忽略前一個信號的值岸军,底層的實現(xiàn)是先過濾之前信號發(fā)的值,再使用concat連接then返回的信號瓦侮。
[[[[[[[self requestAccessToTwitterSignal]
      then:^RACSignal *{
          @strongify(self)
          return self.searchText.rac_textSignal;
      }]
     filter:^BOOL(NSString *text) {
         @strongify(self)
         return [self isValidSearchText:text];
     }]
    throttle:0.5]
   flattenMap:^RACStream *(NSString *text) {
       @strongify(self)
       //flattenMap來將每個next事件映射到一個新的被訂閱的信號
       return [self signalForSearchWithText:text];
   }]
  deliverOn:[RACScheduler mainThreadScheduler]]
 subscribeNext:^(NSDictionary *jsonSearchResult) {
     NSArray *statuses = jsonSearchResult[@"statuses"];
     NSArray *tweets = [statuses linq_select:^id(id tweet) {
         return [RWTweet tweetWithStatus:tweet];
     }];
     [self.resultsViewController displayTweets:tweets];
 } error:^(NSError *error) {
     NSLog(@"An error occurred: %@", error);
 }];
 - (RACSignal *)signalForSearchWithText:(NSString *)text { 
    // 1 - define the errors 
    NSError *noAccountsError = [NSError errorWithDomain:RWTwitterInstantDomain 
                                                   code:RWTwitterInstantErrorNoTwitterAccounts 
                                               userInfo:nil]; 
    NSError *invalidResponseError = [NSError errorWithDomain:RWTwitterInstantDomain 
                                                        code:RWTwitterInstantErrorInvalidResponse 
                                                        userInfo:nil]; 
    @weakify(self) 
    return [RACSignal createSignal:^RACDisposable *(id subscriber) { 
        @strongify(self); 
        SLRequest *request = [self requestforTwitterSearchWithText:text]; 
        NSArray *twitterAccounts = [self.accountStore accountsWithAccountType:self.twitterAccountType];         if (twitterAccounts.count == 0) { 
            [subscriber sendError:noAccountsError]; 
        } else { 
            [request setAccount:[twitterAccounts lastObject]]; 
        [request performRequestWithHandler: ^(NSData *responseData, 
                NSHTTPURLResponse *urlResponse, NSError *error) { 
            if (urlResponse.statusCode == 200) { 
                NSDictionary *timelineData = [NSJSONSerialization JSONObjectWithData:responseData 
                                                options:NSJSONReadingAllowFragments 
                                                  error:nil]; 
                [subscriber sendNext:timelineData]; 
                [subscriber sendCompleted]; 
            } else { 
                [subscriber sendError:invalidResponseError]; 
            } 
        }]; 
    } 
    return nil; 
    }];
}
不同信號順序鏈接
不同信號順序鏈接

異步加載圖片

-(RACSignal *)signalForLoadingImage:(NSString *)imageUrl {

    RACScheduler *scheduler = [RACScheduler
                               schedulerWithPriority:RACSchedulerPriorityBackground];

    return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]];
        UIImage *image = [UIImage imageWithData:data];
        [subscriber sendNext:image];
        [subscriber sendCompleted];
        return nil;
    }] subscribeOn:scheduler];
}

cell.twitterAvatarView.image = nil;
[[[self signalForLoadingImage:tweet.profileImageUrl]
  deliverOn:[RACScheduler mainThreadScheduler]]
  subscribeNext:^(UIImage *image) {
   cell.twitterAvatarView.image = image;
  }];

觀察viewModel里的tableView的數(shù)據(jù)鍵值和全部讀取鍵值艰赞,只要有一個有新值就會調(diào)用

@weakify(self);
[[[RACSignal merge: @[RACObserve(self.viewModel,  tweets), 
                     RACObserve(self.viewModel,  allTweetsLoaded)]]
    bufferWithTime: 0 onScheduler: [RACScheduler mainThreadScheduler]]
    subscribeNext: ^(id value) {
        @strongify(self);
        [self.tableView reloadData];
    }];
//bufferWithTime設(shè)置為0是為了避免同一時刻兩個值被同時設(shè)置新值產(chǎn)生了table進(jìn)行了兩次reloadData

封裝hook方法,某個selector被調(diào)用時肚吏,再執(zhí)行一段指定代碼和hook一樣方妖。

@weakify(self);
[[tableView rac_signalForSelector:@selector(layoutSubviews)]subscribeNext:^(id x) {
    @strongify(self);
    [self doSomethingBeforeTableViewLayoutSubviews];
}];

使用RACCommand來實現(xiàn)按鈕的狀態(tài)根據(jù)輸入郵箱判斷郵箱是否非法還有提交到服務(wù)器后出錯處理等

Demo的github地址:https://github.com/olegam/RACCommandExample

- (void)bindWithViewModel {     
  RAC(self.viewModel, email) =self.emailTextField.rac_textSignal;   
  self.subscribeButton.rac_command = self.viewModel.subscribeCommand;     
  RAC(self.statusLabel, text) =RACObserve(self.viewModel, statusMessage);   
}

@interface SubscribeViewModel :NSObject    
  @property(nonatomic, strong)RACCommand *subscribeCommand;  // writeto this property  
  @property(nonatomic, strong) NSString *email;  // read from this property  
  @property(nonatomic, strong) NSString *statusMessage;    
@end  

#import "SubscribeViewModel.h"  
#import "AFHTTPRequestOperationManager+RACSupport.h"  
#import"NSString+EmailAdditions.h"  

static NSString *const kSubscribeURL =@"http://reactivetest.apiary.io/subscribers";  

@interface SubscribeViewModel ()  
@property(nonatomic, strong) RACSignal*emailValidSignal;  
@end  

@implementation SubscribeViewModel  

- (id)init {  
       self= [super init];  
       if(self) {  
            [self mapSubscribeCommandStateToStatusMessage];  
       }  
       returnself;  
}  

-(void)mapSubscribeCommandStateToStatusMessage {  
       RACSignal *startedMessageSource = [self.subscribeCommand.executionSignals map:^id(RACSignal *subscribeSignal) {  
              return NSLocalizedString(@"Sending request...", nil);  
       }];  

       RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {  
              return[[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {  
                     return event.eventType == RACEventTypeCompleted;  
              }] map:^id(id value) {  
                     return NSLocalizedString(@"Thanks", nil);  
              }];  
       }];  

       RACSignal*failedMessageSource = [[self.subscribeCommand.errors subscribeOn:[RACSchedulermainThreadScheduler]] map:^id(NSError *error) {  
              return NSLocalizedString(@"Error :(", nil);  
       }];  

       RAC(self,statusMessage) = [RACSignal merge:@[startedMessageSource, completedMessageSource, failedMessageSource]];  
}  

- (RACCommand *)subscribeCommand {  
       if(!_subscribeCommand) {  
              @weakify(self);  
              _subscribeCommand = [[RACCommand alloc] initWithEnabled:self.emailValidSignal signalBlock:^RACSignal *(id input) {  
                     @strongify(self);  
                     return [SubscribeViewModel postEmail:self.email];  
              }];  
       }  
       return _subscribeCommand;  
}  

+ (RACSignal *)postEmail:(NSString *)email{  
       AFHTTPRequestOperationManager*manager = [AFHTTPRequestOperationManager manager];  
       manager.requestSerializer= [AFJSONRequestSerializer new];  
       NSDictionary*body = @{@"email": email ?: @""};  
       return [[[manager rac_POST:kSubscribeURL parameters:body] logError] replayLazily];  
}  

- (RACSignal *)emailValidSignal {  
       if(!_emailValidSignal) {  
              _emailValidSignal= [RACObserve(self, email) map:^id(NSString *email) {  
                     return@([email isValidEmail]);  
              }];  
       }  
       return _emailValidSignal;  
}  

@end

替換Delegate,直接使用RACSubject

RAC內(nèi)存管理

RAC會維護(hù)一個全局的信號集合罚攀,一個或多于一個訂閱者就可用党觅,所有訂閱者都被移除了,信號就被釋放了斋泄。

RAC需要注意的內(nèi)存問題

宏定義

- (void)viewDidLoad
{
    [super viewDidLoad];
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { //1
        MTModel *model = [[MTModel alloc] init]; // MTModel有一個名為的title的屬性
        [subscriber sendNext:model];
        [subscriber sendCompleted];
        return nil;
    }];
    self.flattenMapSignal = [signal flattenMap:^RACStream *(MTModel *model) { //2
        return RACObserve(model, title);
    }];
    [self.flattenMapSignal subscribeNext:^(id x) { //3
        NSLog(@"subscribeNext - %@", x);
    }];
}

上面的RACObserve會引起引用不釋放的問題杯瞻,通過RACObserve的定義來看看,里面會對self進(jìn)行持有炫掐。

#define RACObserve(TARGET, KEYPATH) \
    ({ \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
        __weak id target_ = (TARGET); \
        [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
        _Pragma("clang diagnostic pop") \
    })

Subject

對subject進(jìn)行map這樣的操作魁莉,這時就需要sendCompleted

- (void)viewDidLoad {
    [super viewDidLoad];
    RACSubject *subject = [RACSubject subject]; 
    [subject.rac_willDeallocSignal subscribeCompleted:^{ 
        NSLog(@"subject dealloc");
    }];

    [[subject map:^id(NSNumber *value) { 
        return @([value integerValue] * 3);
    }] subscribeNext:^(id x) { 
        NSLog(@"next = %@", x);
    }];
    [subject sendNext:@1]; 
}

但是為什么signal進(jìn)行map操作,不sendCompleted而不會內(nèi)存泄漏呢募胃。因為調(diào)到bind的比如map旗唁、filter、merge痹束、combineLatest检疫、flattenMap等操作如果是RACSubject這樣會持有訂閱者的信號會產(chǎn)生內(nèi)存泄漏需要sendCompleted〉凰唬可以先看看bind的實現(xiàn)

- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
    NSCParameterAssert(block != NULL);
    /*
     * -bind: should:
     * 
     * 1. Subscribe to the original signal of values.
     * 2. Any time the original signal sends a value, transform it using the binding block.
     * 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
     * 4. If the binding block asks the bind to terminate, complete the _original_ signal.
     * 5. When _all_ signals complete, send completed to the subscriber.
     * 
     * If any signal sends an error at any point, send that to the subscriber.
     */
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACStreamBindBlock bindingBlock = block();
        NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
        // 此處省略了80行代碼
        // ...
    }] setNameWithFormat:@"[%@] -bind:", self.name];
}

didSubscribe的開頭屎媳,就創(chuàng)建了一個數(shù)組signals,并且持有了self论巍,也就是源信號剿牺,也就是訂閱者持有了信號,如果是Subject那么這種信號又會持有訂閱者环壤,這樣就形成了循環(huán)引用晒来。

下面看看sendCompleted如何修復(fù)的內(nèi)存泄漏

void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {
    BOOL removeDisposable = NO;
    @synchronized (signals) {
        [signals removeObject:signal]; //1
        if (signals.count == 0) {
            [subscriber sendCompleted]; //2
            [compoundDisposable dispose]; //3
        } else {
            removeDisposable = YES;
        }
    }
    if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable]; //4
};

從signals這個數(shù)組中移除傳入的signal,也就是讓訂閱的signal不會持有subject這種信號郑现。

還有replay這樣的操作湃崩,因為這個方法返回的是一個RACReplaySubject

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@1];
    [subscriber sendCompleted]; // 保證源信號發(fā)送完成
    return nil;
}];

RACSignal *replaySignal = [signal replay]; // 這里返回的其實是一個RACReplaySubject

[[replaySignal map:^id(NSNumber *value) {
    return @([value integerValue] * 3);
}] subscribeNext:^(id x) {
    NSLog(@"subscribeNext - %@", x);
}];

熱信號冷信號

  • 熱信號是主動的荧降,不訂閱也能夠按時發(fā)送。冷信號是被動的攒读,只有訂閱才會發(fā)送朵诫。
  • 熱信號可以有多個訂閱者。冷信號只能夠一對一薄扁,有不同訂閱者剪返,消息會從新完整發(fā)送。

RAC的API手冊

常見類

RACSiganl 信號類邓梅。

  • RACEmptySignal :空信號脱盲,用來實現(xiàn) RACSignal 的 +empty 方法;
  • RACReturnSignal :一元信號日缨,用來實現(xiàn) RACSignal 的 +return: 方法钱反;
  • RACDynamicSignal :動態(tài)信號,使用一個 block - 來實現(xiàn)訂閱行為匣距,我們在使用 RACSignal 的 +createSignal: 方法時創(chuàng)建的就是該類的實例面哥;
  • RACErrorSignal :錯誤信號,用來實現(xiàn) RACSignal 的 +error: 方法毅待;
  • RACChannelTerminal :通道終端尚卫,代表 RACChannel 的一個終端,用來實現(xiàn)雙向綁定尸红。

RACSubscriber 訂閱者

RACDisposable 用于取消訂閱或者清理資源吱涉,當(dāng)信號發(fā)送完成或者發(fā)送錯誤的時候,就會自動觸發(fā)它驶乾。

  • RACSerialDisposable :作為 disposable 的容器使用,可以包含一個 disposable 對象循签,并且允許將這個 disposable 對象通過原子操作交換出來;
  • RACKVOTrampoline :代表一次 KVO 觀察,并且可以用來停止觀察呐馆;
  • RACCompoundDisposable :它可以包含多個 disposable 對象斯议,并且支持手動添加和移除 disposable 對象
  • RACScopedDisposable :當(dāng)它被 dealloc 的時候調(diào)用本身的 -dispose 方法。

RACSubject 信號提供者乞旦,自己可以充當(dāng)信號贼穆,又能發(fā)送信號。訂閱后發(fā)送

  • RACGroupedSignal :分組信號兰粉,用來實現(xiàn) RACSignal 的分組功能故痊;
  • RACBehaviorSubject :重演最后值的信號,當(dāng)被訂閱時玖姑,會向訂閱者發(fā)送它最后接收到的值愕秫;
  • RACReplaySubject :重演信號慨菱,保存發(fā)送過的值,當(dāng)被訂閱時戴甩,會向訂閱者重新發(fā)送這些值符喝。可以先發(fā)送后訂閱

RACTuple 元組類,類似NSArray,用來包裝值.

RACSequence RAC中的集合類

RACCommand RAC中用于處理事件的類甜孤,可以把事件如何處理,事件中的數(shù)據(jù)如何傳遞协饲,包裝到這個類中,他可以很方便的監(jiān)控事件的執(zhí)行過程缴川。

RACMulticastConnection 用于當(dāng)一個信號茉稠,被多次訂閱時,為了保證創(chuàng)建信號時二跋,避免多次調(diào)用創(chuàng)建信號中的block战惊,造成副作用,可以使用這個類處理扎即。

RACScheduler RAC中的隊列吞获,用GCD封裝的。

  • RACImmediateScheduler :立即執(zhí)行調(diào)度的任務(wù)谚鄙,這是唯一一個支持同步執(zhí)行的調(diào)度器各拷;
  • RACQueueScheduler :一個抽象的隊列調(diào)度器,在一個 GCD 串行列隊中異步調(diào)度所有任務(wù)闷营;
  • RACTargetQueueScheduler :繼承自 RACQueueScheduler 烤黍,在一個以一個任意的 GCD 隊列為 target 的串行隊列中異步調(diào)度所有任務(wù);
  • RACSubscriptionScheduler :一個只用來調(diào)度訂閱的調(diào)度器傻盟。

常見用法

  • rac_signalForSelector : 代替代理
  • rac_valuesAndChangesForKeyPath: KVO
  • rac_signalForControlEvents:監(jiān)聽事件
  • rac_addObserverForName 代替通知
  • rac_textSignal:監(jiān)聽文本框文字改變
  • rac_liftSelector:withSignalsFromArray:Signals:當(dāng)傳入的Signals(信號數(shù)組)速蕊,每一個signal都至少sendNext過一次,就會去觸發(fā)第一個selector參數(shù)的方法娘赴。

常見宏

  • RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用于給某個對象的某個屬性綁定
  • RACObserve(self, name) :監(jiān)聽某個對象的某個屬性,返回的是信號规哲。
  • @weakify(Obj)和@strongify(Obj)
  • RACTuplePack :把數(shù)據(jù)包裝成RACTuple(元組類)
  • RACTupleUnpack:把RACTuple(元組類)解包成對應(yīng)的數(shù)據(jù)
  • RACChannelTo 用于雙向綁定的一個終端

常用操作方法

  • flattenMap map 用于把源信號內(nèi)容映射成新的內(nèi)容。
  • concat 組合 按一定順序拼接信號诽表,當(dāng)多個信號發(fā)出的時候唉锌,有順序的接收信號
  • then 用于連接兩個信號,當(dāng)?shù)谝粋€信號完成竿奏,才會連接then返回的信號袄简。
  • merge 把多個信號合并為一個信號,任何一個信號有新值的時候就會調(diào)用
  • zipWith 把兩個信號壓縮成一個信號泛啸,只有當(dāng)兩個信號同時發(fā)出信號內(nèi)容時绿语,并且把兩個信號的內(nèi)容合并成一個元組,才會觸發(fā)壓縮流的next事件。
  • combineLatest:將多個信號合并起來汞舱,并且拿到各個信號的最新的值,必須每個合并的signal至少都有過一次sendNext伍纫,才會觸發(fā)合并的信號。
  • reduce聚合:用于信號發(fā)出的內(nèi)容是元組昂芜,把信號發(fā)出元組的值聚合成一個值
  • filter:過濾信號莹规,使用它可以獲取滿足條件的信號.
  • ignore:忽略完某些值的信號.
  • distinctUntilChanged:當(dāng)上一次的值和當(dāng)前的值有明顯的變化就會發(fā)出信號,否則會被忽略掉泌神。
  • take:從開始一共取N次的信號
  • takeLast:取最后N次的信號,前提條件良漱,訂閱者必須調(diào)用完成,因為只有完成欢际,就知道總共有多少信號.
  • takeUntil:(RACSignal *):獲取信號直到某個信號執(zhí)行完成
  • skip:(NSUInteger):跳過幾個信號,不接受母市。
  • switchToLatest:用于signalOfSignals(信號的信號),有時候信號也會發(fā)出信號损趋,會在signalOfSignals中患久,獲取signalOfSignals發(fā)送的最新信號。
  • doNext: 執(zhí)行Next之前浑槽,會先執(zhí)行這個Block
  • doCompleted: 執(zhí)行sendCompleted之前蒋失,會先執(zhí)行這個Block
  • timeout:超時,可以讓一個信號在一定的時間后桐玻,自動報錯篙挽。
  • interval 定時:每隔一段時間發(fā)出信號
  • delay 延遲發(fā)送next。
  • retry重試 :只要失敗镊靴,就會重新執(zhí)行創(chuàng)建信號中的block,直到成功.
  • replay重放:當(dāng)一個信號被多次訂閱,反復(fù)播放內(nèi)容
  • throttle節(jié)流:當(dāng)某個信號發(fā)送比較頻繁時铣卡,可以使用節(jié)流,在某一段時間不發(fā)送信號內(nèi)容偏竟,過了一段時間獲取信號的最新內(nèi)容發(fā)出煮落。

UI - Category(常用匯總)

rac_prepareForReuseSignal: 需要復(fù)用時用

  • 相關(guān)UI: MKAnnotationView、UICollectionReusableView踊谋、UITableViewCell蝉仇、UITableViewHeaderFooterView

rac_buttonClickedSignal:點擊事件觸發(fā)信號

  • 相關(guān)UI:UIActionSheet、UIAlertView

rac_command:button類褪子、刷新類相關(guān)命令替換

  • 相關(guān)UI:UIBarButtonItem量淌、UIButton骗村、UIRefreshControl

rac_signalForControlEvents: control event 觸發(fā)

  • 相關(guān)UI:UIControl

rac_gestureSignal UIGestureRecognizer 事件處理信號

  • 相關(guān)UI:UIGestureRecognizer

rac_imageSelectedSignal 選擇圖片的信號

  • 相關(guān)UI:UIImagePickerController

rac_textSignal

  • 相關(guān)UI:UITextField嫌褪、UITextView

可實現(xiàn)雙向綁定的相關(guān)API

  • rac_channelForControlEvents: key: nilValue:
  • 相關(guān)UI:UIControl類
  • rac_newDateChannelWithNilValue:
  • 相關(guān)UI:UIDatePicker
  • rac_newSelectedSegmentIndexChannelWithNilValue:
  • 相關(guān)UI:UISegmentedControl
  • rac_newValueChannelWithNilValue:
  • 相關(guān)UI:UISlider、UIStepper
  • rac_newOnChannel
  • 相關(guān)UI:UISwitch
  • rac_newTextChannel
  • 相關(guān)UI:UITextField

Foundation - Category (常用匯總)

NSData

  • rac_readContentsOfURL: options: scheduler: 比oc多出線程設(shè)置

NSDictionary

  • rac_sequence
  • rac_keySequence key 集合
  • rac_valueSequence value 集合

NSArray

  • rac_sequence 信號集合

NSFileHandle

  • rac_readInBackground 后臺線程讀取

NSInvocation

  • rac_setArgument: atIndex: 設(shè)置參數(shù)
  • rac_argumentAtIndex 取某個參數(shù)
  • rac_returnValue 所關(guān)聯(lián)方法的返回值

NSNotificationCenter

  • rac_addObserverForName: object:注冊通知

NSObject

  • rac_willDeallocSignal 對象銷毀時發(fā)動的信號
  • rac_description debug用
  • rac_observeKeyPath: options: observer: block:監(jiān)聽某個事件
  • rac_liftSelector: withSignals: 全部信號都next在執(zhí)行
  • rac_signalForSelector: 代替某個方法
  • rac_signalForSelector:(SEL)selector fromProtocol:代替代理

NSString

  • rac_keyPathComponents 獲取一個路徑所有的部分
  • rac_keyPathByDeletingLastKeyPathComponent 刪除路徑最后一部分
  • rac_keyPathByDeletingFirstKeyPathComponent 刪除路徑第一部分
  • rac_readContentsOfURL: usedEncoding: scheduler: 比之OC多線程調(diào)用
  • rac_sequence

NSURLConnection

  • rac_sendAsynchronousRequest 發(fā)起異步請求

NSUserDefaults

  • rac_channelTerminalForKey 用于雙向綁定胚股,此乃一

NSEnumerator

  • rac_sequence

NSIndexSet

  • rac_sequence

NSOrderedSet

  • rac_sequence

NSSet

  • rac_sequence

RAC圖片版的API手冊

ReactiveCocoa Objective-C

ReactiveCocoaObjective-C
ReactiveCocoaObjective-C

ReactiveCocoa Swift

ReactiveCocoaSwift
ReactiveCocoaSwift

RXSwift

RXSwift
RXSwift

本文參考整理自

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末笼痛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缨伊,老刑警劉巖摘刑,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異刻坊,居然都是意外死亡枷恕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門谭胚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來徐块,“玉大人,你說我怎么就攤上這事灾而『兀” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵旁趟,是天一觀的道長昼激。 經(jīng)常有香客問我,道長锡搜,這世上最難降的妖魔是什么橙困? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮余爆,結(jié)果婚禮上纷宇,老公的妹妹穿的比我還像新娘。我一直安慰自己蛾方,他們只是感情好像捶,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桩砰,像睡著了一般拓春。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亚隅,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天硼莽,我揣著相機(jī)與錄音,去河邊找鬼煮纵。 笑死懂鸵,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的行疏。 我是一名探鬼主播匆光,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酿联!你這毒婦竟也來了终息?” 一聲冷哼從身側(cè)響起夺巩,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎周崭,沒想到半個月后柳譬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡续镇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年美澳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摸航。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡人柿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出忙厌,到底是詐尸還是另有隱情凫岖,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布逢净,位于F島的核電站哥放,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏爹土。R本人自食惡果不足惜甥雕,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胀茵。 院中可真熱鬧社露,春花似錦、人聲如沸琼娘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脱拼。三九已至瞒瘸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熄浓,已是汗流浹背情臭。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留赌蔑,地道東北人俯在。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像娃惯,于是被迫代替她去往敵國和親跷乐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容