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 ==>此文的初始工程
以及譯文