ReactiveCocoa 學習 一

ReactiveCocoa 框架,在剛聽說過這個框架時,我便在github上搜索了一下,star的數量確實讓我震驚了一下。查詢資料了解后,感覺自己入行沒多久,感覺這個框架高深莫測,不敢涉獵。
這個周末的閑暇之余,我決定學習一下這個框架并寫個登錄demo。

開始了。
首先來導入框架,我使用的是Cocopods來導入框架。畢竟這個框架手動導入起來實在是太麻煩??

通過stroyboard搭建了一個登錄界面,并聲明了三個變量:

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *userTF;
@property (weak, nonatomic) IBOutlet UITextField *passTF;
@property (weak, nonatomic) IBOutlet UIButton *loginBTN;
@property (nonatomic, strong) LoginService *service; //這是自己寫的模擬后臺
@end

導入框架后,在view controller中引入ReactiveCocoa的頭文件

#import <ReactiveCocoa/ReactiveCocoa.h> 

在viewdidload中寫上代碼:

RACSignal *userTFSingal = self.userTF.rac_textSignal;
RACSignal *passTFSingal = self.passTF.rac_textSignal;

[userTFSingal subscribeNext:^(id x) {
        NSLog(@"打印出來的用戶名文本:%@",x);
    }];

運行之后會發現 userTF中值發生了改變就會打印一次userTF中的text;

也可以通過fliter來設定subscribeNext響應條件。

[[userTFSingal filter:^BOOL(NSString *text) {
        
        return text.length > 3;
    }] subscribeNext:^(id x) {
        NSLog(@"用戶名超過三位的文本:%@",x);
    }];

map操作通過block改變了事件的數據。map從上一個next事件接收數據,通過執行block把返回值傳給下一個next事件。在上面的代碼中,map以NSString為輸入,取字符串的長度,返回一個NSNumber。

    [[[userTFSingal map:^id(NSString *text) {
        return @(text.length);
    }]
    filter:^BOOL(NSNumber *length) {
        return [length intValue]>5;
    }]
    subscribeNext:^(id x) {
        NSLog(@"用戶名文本長度:%@",x);
    }];

上面說明了ReactiveCocoa的UITextField部分使用。下面開始實現登錄邏輯吧!

設置有效文本長度


- (BOOL)isValidUsername:(NSString *)username {
    return username.length > 3;
}

- (BOOL)isValidPassword:(NSString *)password {
    return password.length > 3;
}

創建有效狀態信號

    RACSignal *validUsernameSignal = [self.userTF.rac_textSignal 
map:^id(NSString *text) {
        return @([self isValidUsername:text]);
    }];
    RACSignal *validPasswordSignal = [self.passTF.rac_textSignal
     map:^id(NSString *text) {
         return @([self isValidPassword:text]);
     }];

根據文本是否有效改變文本框顏色

    [[validPasswordSignal map:^id(NSNumber *passValid) {
        return [passValid boolValue] ? [UIColor clearColor] : [UIColor grayColor];
    }]
     subscribeNext:^(UIColor *color) {
        self.passTF.backgroundColor = color;
    }];

相對于上面這個方法更推薦使用下面這個方法:


RAC(self.userTF, backgroundColor) = [validUsernameSignal map:^id(NSNumber *userValid) {
        return [userValid boolValue] ? [UIColor clearColor] : [UIColor purpleColor];
    }];

    
    RAC(self.passTF, backgroundColor) = [validPasswordSignal map:^id(NSNumber *passValid) {
        return [passValid boolValue] ? [UIColor clearColor] : [UIColor purpleColor];
    }];

聚合信號
combineLatest:reduce:方法把validUsernameSignal和validPasswordSignal產生的最新的值聚合在一起,并生成一個新的信號。每次這兩個源信號的任何一個產生新值時,reduce block都會執行,block的返回值會發給下一個信號。


RACSignal *loginSignal = [RACSignal combineLatest:@[validPasswordSignal,validUsernameSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
        return @([usernameValid boolValue] && [passwordValid boolValue]);
    }];

這里是模擬后臺

LoginService接口


typedef void (^RWSignInResponse)(BOOL);

@interface LoginService : NSObject

- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock;

@end

實現


- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock {
    
    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){
        BOOL success = [username isEqualToString:@"user"] && [password isEqualToString:@"password"];
        completeBlock(success);
    });
}

創建登錄信號

- (RACSignal *)loginInSignal {
    
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [self.service signInWithUsername:self.userTF.text password:self.passTF.text complete:^(BOOL success) {
            [subscriber sendNext:@(success)];
            [subscriber sendCompleted];
        }];
        return nil;
    }];
}

UIButton

設置按鈕狀態

[loginSignal subscribeNext:^(NSNumber *login) {
        self.loginBTN.enabled = [login boolValue];
    }];

UIButton部分使用

[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        NSLog(@"登錄按鈕被點擊了");
    }];

輸出登錄結果

    [[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) {
        return [self loginInSignal];
    }] subscribeNext:^(id x) {
        NSLog(@"登錄結果:%@",x);
    }];

這里你會發現打印的登錄結果 不是bool類型
當點擊按鈕時,rac_signalForControlEvents發送了一個next事件(事件的data是UIButton)。map操作創建并返回了登錄信號,這意味著后續步驟都會收到一個RACSignal。這就是你在subscribeNext:這步看到的。

上面問題的解決方法,有時候叫做信號中的信號,換句話說就是一個外部信號里面還有一個內部信號。你可以在外部信號的subscribeNext:block里訂閱內部信號。不過這樣嵌套太混亂啦,還好ReactiveCocoa已經解決了這個問題。

信號中的信號
解決的方法很簡單,只需要把map操作改成flattenMap就可以了:

        [[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^id(id value) {
            return [self loginInSignal];
        }] subscribeNext:^(NSNumber *loginIn) {
            NSLog(@"登錄結果:%@",loginIn);
            BOOL success = [loginIn boolValue];
            if (success) {
                [self performSegueWithIdentifier:@"Kitten" sender:self];
            }
        }];

為了防止多次點擊Button,使用doNext添加附加操作。

    [[[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
        self.loginBTN.enabled = NO;
    }] flattenMap:^RACStream *(id value) {
        return [self loginInSignal];
    }] subscribeNext:^(NSNumber *loginIn) {
        NSLog(@"登錄結果:%@",loginIn);
                    BOOL success = [loginIn boolValue];
                    if (success) {
                        [self performSegueWithIdentifier:@"Kitten" sender:self];
                    }

    }];

好了 登錄demo完成了。
參考文章:
最快讓你上手ReactiveCocoa之基礎篇
ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 ==>此文的初始工程
以及譯文

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容