ReactiveCocoa 框架,在剛聽說過這個(gè)框架時(shí),我便在github上搜索了一下,star的數(shù)量確實(shí)讓我震驚了一下。查詢資料了解后,感覺自己入行沒多久,感覺這個(gè)框架高深莫測,不敢涉獵。
這個(gè)周末的閑暇之余,我決定學(xué)習(xí)一下這個(gè)框架并寫個(gè)登錄demo。
開始了。
首先來導(dǎo)入框架,我使用的是Cocopods來導(dǎo)入框架。畢竟這個(gè)框架手動(dòng)導(dǎo)入起來實(shí)在是太麻煩??
通過stroyboard搭建了一個(gè)登錄界面,并聲明了三個(gè)變量:
@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; //這是自己寫的模擬后臺(tái)
@end
導(dǎo)入框架后,在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);
}];
運(yùn)行之后會(huì)發(fā)現(xiàn) userTF中值發(fā)生了改變就會(huì)打印一次userTF中的text;
也可以通過fliter來設(shè)定subscribeNext響應(yīng)條件。
[[userTFSingal filter:^BOOL(NSString *text) {
return text.length > 3;
}] subscribeNext:^(id x) {
NSLog(@"用戶名超過三位的文本:%@",x);
}];
map操作通過block改變了事件的數(shù)據(jù)。map從上一個(gè)next事件接收數(shù)據(jù),通過執(zhí)行block把返回值傳給下一個(gè)next事件。在上面的代碼中,map以NSString為輸入,取字符串的長度,返回一個(gè)NSNumber。
[[[userTFSingal map:^id(NSString *text) {
return @(text.length);
}]
filter:^BOOL(NSNumber *length) {
return [length intValue]>5;
}]
subscribeNext:^(id x) {
NSLog(@"用戶名文本長度:%@",x);
}];
上面說明了ReactiveCocoa的UITextField部分使用。下面開始實(shí)現(xiàn)登錄邏輯吧!
設(shè)置有效文本長度
- (BOOL)isValidUsername:(NSString *)username {
return username.length > 3;
}
- (BOOL)isValidPassword:(NSString *)password {
return password.length > 3;
}
創(chuàng)建有效狀態(tài)信號(hào)
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]);
}];
根據(jù)文本是否有效改變文本框顏色
[[validPasswordSignal map:^id(NSNumber *passValid) {
return [passValid boolValue] ? [UIColor clearColor] : [UIColor grayColor];
}]
subscribeNext:^(UIColor *color) {
self.passTF.backgroundColor = color;
}];
相對(duì)于上面這個(gè)方法更推薦使用下面這個(gè)方法:
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];
}];
聚合信號(hào)
combineLatest:reduce:方法把validUsernameSignal和validPasswordSignal產(chǎn)生的最新的值聚合在一起,并生成一個(gè)新的信號(hào)。每次這兩個(gè)源信號(hào)的任何一個(gè)產(chǎn)生新值時(shí),reduce block都會(huì)執(zhí)行,block的返回值會(huì)發(fā)給下一個(gè)信號(hào)。
RACSignal *loginSignal = [RACSignal combineLatest:@[validPasswordSignal,validUsernameSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue] && [passwordValid boolValue]);
}];
這里是模擬后臺(tái)
LoginService接口
typedef void (^RWSignInResponse)(BOOL);
@interface LoginService : NSObject
- (void)signInWithUsername:(NSString *)username password:(NSString *)password complete:(RWSignInResponse)completeBlock;
@end
實(shí)現(xiàn)
- (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);
});
}
創(chuàng)建登錄信號(hào)
- (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
設(shè)置按鈕狀態(tài)
[loginSignal subscribeNext:^(NSNumber *login) {
self.loginBTN.enabled = [login boolValue];
}];
UIButton部分使用
[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"登錄按鈕被點(diǎn)擊了");
}];
輸出登錄結(jié)果
[[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] map:^id(id value) {
return [self loginInSignal];
}] subscribeNext:^(id x) {
NSLog(@"登錄結(jié)果:%@",x);
}];
這里你會(huì)發(fā)現(xiàn)打印的登錄結(jié)果 不是bool類型
當(dāng)點(diǎn)擊按鈕時(shí),rac_signalForControlEvents發(fā)送了一個(gè)next事件(事件的data是UIButton)。map操作創(chuàng)建并返回了登錄信號(hào),這意味著后續(xù)步驟都會(huì)收到一個(gè)RACSignal。這就是你在subscribeNext:這步看到的。
上面問題的解決方法,有時(shí)候叫做信號(hào)中的信號(hào),換句話說就是一個(gè)外部信號(hào)里面還有一個(gè)內(nèi)部信號(hào)。你可以在外部信號(hào)的subscribeNext:block里訂閱內(nèi)部信號(hào)。不過這樣嵌套太混亂啦,還好ReactiveCocoa已經(jīng)解決了這個(gè)問題。
信號(hào)中的信號(hào)
解決的方法很簡單,只需要把map操作改成flattenMap就可以了:
[[[self.loginBTN rac_signalForControlEvents:UIControlEventTouchUpInside] flattenMap:^id(id value) {
return [self loginInSignal];
}] subscribeNext:^(NSNumber *loginIn) {
NSLog(@"登錄結(jié)果:%@",loginIn);
BOOL success = [loginIn boolValue];
if (success) {
[self performSegueWithIdentifier:@"Kitten" sender:self];
}
}];
為了防止多次點(diǎn)擊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(@"登錄結(jié)果:%@",loginIn);
BOOL success = [loginIn boolValue];
if (success) {
[self performSegueWithIdentifier:@"Kitten" sender:self];
}
}];
好了 登錄demo完成了。
參考文章:
最快讓你上手ReactiveCocoa之基礎(chǔ)篇
ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2 ==>此文的初始工程
以及譯文