本文記錄一些關于學習ReactiveCocoa基礎知識內容,對于ReactiveCocoa相關的概念如果不了解可以網上搜索;RACSignal有很多方法可以來訂閱不同的事件類型,ReactiveCocoa框架使用category來為很多基本UIKit控件添加signal。本文有收集一些網上其它文章的實例跟內容;
一:先創建頁面布局(準備階段)
@interface ViewController ()@PRoperty(strong,nonatomic)UITextField *nameTextField;@property(strong,nonatomic)UILabel *contentLabel;@property(strong,nonatomic)UIButton *saveBtn;@end
if (self.nameTextField==nil) { self.nameTextField=[[UITextField alloc]init]; self.nameTextField.backgroundColor=[UIColor grayColor]; [self.view addSubview:self.nameTextField]; [self.nameTextField mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(self.view.mas_top).with.offset(64); make.left.mas_equalTo(self.view.mas_left).with.offset(0); make.right.mas_equalTo(self.view.mas_right).with.offset(0); make.height.mas_equalTo(@40); }]; } if (self.contentLabel==nil) { self.contentLabel=[[UILabel alloc]init]; self.contentLabel.backgroundColor=[UIColor blueColor]; [self.view addSubview:self.contentLabel]; [self.contentLabel mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(self.nameTextField.mas_bottom).with.offset(10); make.left.mas_equalTo(self.view.mas_left).with.offset(0); make.right.mas_equalTo(self.view.mas_right).with.offset(0); make.height.mas_equalTo(@100); }]; } if (self.saveBtn==nil) { self.saveBtn=[[UIButton alloc]init]; self.saveBtn.backgroundColor=[UIColor blackColor]; [self.saveBtn setTitle:@"提交" forState:UIControlStateNormal]; [self.view addSubview:self.saveBtn]; [self.saveBtn mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(self.contentLabel.mas_bottom).with.offset(20); make.left.mas_equalTo(self.view.mas_left).with.offset(30); make.right.mas_equalTo(self.view.mas_right).with.offset(-30); make.height.mas_equalTo(@40); }]; }
注意:saveBtn這個沒有增加事件的調用代碼,可以直接運用ReactiveCocoa給它注冊事件,并把相應的操作綁定;
二:ReactiveCocoa小實例(控件創建完后直接把這些綁定在viewDidLoad中)
1:UITextField的rac_textSignal,它會在文本發生變化時產生信號
[self.nameTextField.rac_textSignal subscribeNext:^(id x) { self.contentLabel.text=x; }];
效果:輸入馬上會把變化的值顯示出來;省去以前還要去監聽的操作;
2:filter條件過濾
[[self.nameTextField.rac_textSignal filter:^BOOL(id value) { NSString *text=value; return text.length>3; }] subscribeNext:^(id x) { self.contentLabel.text=x; }];
效果:只有輸入的字符串長度大于3才會顯示出來,顯示為字符串的內容;
3:拆分寫法,rac_textSignal跟filter都是RACSignal
RACSignal *nameRacSignal=self.nameTextField.rac_textSignal; RACSignal *filteredName=[nameRacSignal filter:^BOOL(id value) { NSString *text=value; return text.length>3; }]; [filteredName subscribeNext:^(id x) { self.contentLabel.text=x; }];
效果:跟實例2上面的效果一樣,只是分開定義;
4:上面所有的id類型都可以根據實際的情況進行類型對應
[[self.nameTextField.rac_textSignal filter:^BOOL(NSString *text){ return text.length > 3; }] subscribeNext:^(id x){ self.contentLabel.text=x; }];
效果:這邊filter里面為NSString類型;其它也可以相應的對照比如int bool等,效果如上
5:map 改變當前的值傳給下個
[[[self.nameTextField.rac_textSignal map:^id(NSString *text){ return @(text.length); }] filter:^BOOL(NSNumber *length){ return [length intValue] > 3; }] subscribeNext:^(id x){ //記得轉換顯示 目前為NSNumber型 self.contentLabel.text=[NSString stringWithFormat:@"%@",x]; }];
效果:最后顯示為:4,5,6,7,8,9.....,不會再顯示字符串的內容,已經被map修改成length,所以filter參數也被改變了;運用可以用來轉換成相要的對象
RAC(self.contentLabel, text) = [[[self.nameTextField.rac_textSignal startWith:@"key is >3"] // startWith 一開始返回的初始值 filter:^BOOL(NSString *value) { // filter使滿足條件的值才能傳出 return value.length > 3; }] map:^id(NSString *text) { //當值為wujy時顯示為bingo! return [text isEqualToString:@"wujy"] ? @"bingo!" : text; }];
6:驗證有效性,并把對應的屬性進行修改(寫法不好,見7點,宏定義RAC)
RACSignal *validUsernameSignal = [self.nameTextField.rac_textSignal map:^id(NSString *text) { return @([self isValidUsername:text]); }]; [[validUsernameSignal map:^id(NSNumber *userNameValid){ return[userNameValid boolValue] ? [UIColor redColor]:[UIColor yellowColor]; }] subscribeNext:^(UIColor *color){ self.contentLabel.backgroundColor = color; }];方法代碼如下:-(BOOL)isValidUsername:(NSString *)userName{ if ([userName isEqualToString:@"wjy"]) { return true; } else { return false; }}
效果:只有當輸入的字符串為wjy時才會改變背景效果;
7:宏RAC的運用
RACSignal *validUsernameSignal = [self.nameTextField.rac_textSignal map:^id(NSString *text) { return @([self isValidUsername:text]); }]; RAC(self.contentLabel,backgroundColor)=[validUsernameSignal map:^id(NSNumber *userNameValid){ return[userNameValid boolValue] ? [UIColor redColor]:[UIColor yellowColor]; }];
效果:這種是簡化的寫法,直接用宏RAC進行,可以在目前的管道中移除subscribeNext:block,轉而使用RAC宏
說明:RAC宏允許直接把信號的輸出應用到對象的屬性上。RAC宏有兩個參數,第一個是需要設置屬性值的對象,第二個是屬性名。每次信號產生一個next事件,傳遞過來的值都會應用到該屬性上。也可以有三個參數:RAC(self.outputLabel, text, @"收到nil時就顯示我") = self.inputTextField.rac_textSignal;第三個是為nil時顯示的內容;
8:聚合信號(引用)
RACSignal *validUsernameSignal = [self.usernameTextField.rac_textSignal map:^id(NSString *text) { return @([self isValidUsername:text]); }]; RACSignal *validPassWordSignal = [self.passwordTextField.rac_textSignal map:^id(NSString *text) { return @([self isValidPassword:text]); }];
RACSignal *signUpActiveSignal = [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal] reduce:^id(NSNumber*usernameValid, NSNumber *passwordValid){ return @([usernameValid boolValue]&&[passwordValid boolValue]); }];
[signUpActiveSignal subscribeNext:^(NSNumber*signupActive){ self.signInButton.enabled =[signupActive boolValue]; }];
說明:RACsignal的這個方法可以聚合任意數量的信號,reduce block的參數和每個源信號相關。ReactiveCocoa有一個工具類RACBlockTrampoline,它在內部處理reduce block的可變參數。
效果:上面的代碼使用combineLatest:reduce:方法把validUsernameSignal和validPasswordSignal產生的最新的值聚合在一起,并生成一個新的信號。每次這兩個源信號的任何一個產生新值時,reduce block都會執行,block的返回值會發給下一個信號。
9:UIButton事件rac_signalForControlEvents
[[self.saveBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { NSLog(@"button clicked"); }];
效果:點擊事件響應
10:doNext
[[[self.saveBtn rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) { self.contentLabel.backgroundColor=[UIColor greenColor]; } ] subscribeNext:^(id x) { NSLog(@"button clicked"); self.contentLabel.backgroundColor=[UIColor redColor]; }];
說明doNext:是直接跟在按鈕點擊事件的后面。而且doNext: block并沒有返回值。因為它是附加操作,并不改變事件本身,功能如:上面的doNext: block把按鈕置為不可點擊,隱藏登錄失敗提示。然后在subscribeNext: block里重新把按鈕置為可點擊,并根據登錄結果來決定是否顯示失敗提示。實例如下
實例: [[[[self.saveBtn rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x){ self.signInButton.enabled =NO; self.signInFailureText.hidden =YES; }] flattenMap:^id(id x){ return[self signInSignal]; }] subscribeNext:^(NSNumber*signedIn){ self.signInButton.enabled =YES; BOOL success =[signedIn boolValue]; self.signInFailureText.hidden = success; if(success){ [self performSegueWithIdentifier:@"signInSuccess" sender:self]; } }];
11:RACObserve運用
RAC(self.outputLabel, text) = RACObserve(self.model, name);
上面的代碼將label的輸出和model的name屬性綁定,實現聯動,name但凡有變化都會使得label輸出
實例:
self.myModel=[[userModel alloc]init]; RAC(self.contentLabel, text) = RACObserve(self.myModel, name); [[self.saveBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { self.myModel.name=@"wujysfsfsdfsf"; }];
注意:userModel是自定義的一個實體,里面只有一個name的屬性,上面運用時記得實例化;
12:自定義信號RACSubject(繼承自RACSignal,可以理解為自由度更高的signal)
- (void)doTest{ RACSubject *subject = [self doRequest]; [subject subscribeNext:^(NSString *value){ NSLog(@"value:%@", value); }];}- (RACSubject *)doRequest{ RACSubject *subject = [RACSubject subject]; // 模擬2秒后得到請求內容 // 只觸發1次 // 盡管subscribeNext什么也沒做,但如果沒有的話map是不會執行的 // subscribeNext就是定義了一個接收體 [[[[RACSignal interval:2] take:1] map:^id(id _){ // the value is from url request NSString *value = @"content fetched from web"; [subject sendNext:value]; return nil; }] subscribeNext:^(id _){}]; return subject;}
13:ignore
[[self.nameTextField.rac_textSignal ignore:@"wjy"] subscribeNext:^(NSString *value) { NSLog(@"當輸入為wjy時會被忽略: %@", value); }];
說明:忽略給定的值,注意,這里忽略的既可以是地址相同的對象,也可以是- isEqual:結果相同的值,也就是說自己寫的Model對象可以通過重寫- isEqual:方法來使- ignore:生效。
14:distinctUntilChanged
self.myModel=[[userModel alloc]init]; RAC(self.contentLabel, text) = [RACObserve(self.myModel, name) distinctUntilChanged]; self.myModel.name = @"第一次"; // 1st self.myModel.name = @"第一次"; // 2nd self.myModel.name = @"第一次"; // 3rd
說明:它將這一次的值與上一次做比較,當相同時(也包括- isEqual:)被忽略掉。如果不增加distinctUntilChanged的話對于連續的相同的輸入值就會有不必要的處理,這個實例只是簡單的UI刷新,但遇到如寫數據庫,發網絡請求的情況時,代價就不能購忽略了。
15:起止點過濾類型
除了被動的當next值來的時候做判斷,也可以主動的提前選擇開始和結束條件,分為兩種類型:take型(取)和skip型(跳)
a:-take: (NSUInteger)
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"1"]; [subscriber sendNext:@"2"]; [subscriber sendNext:@"3"]; [subscriber sendCompleted]; return nil;}] take:2] subscribeNext:^(id x) { NSLog(@"only 1 and 2 will be print: %@", x);}];
說明:從開始一共取N次的next值,不包括Competion
和Error
b:takeLast: (NSUInteger)
取最后N次的next值,注意,由于一開始不能知道這個Signal將有多少個next值,所以RAC實現它的方法是將所有next值都存起來,然后原Signal完成時再將后N個依次發送給接收者,但Error發生時依然是立刻發送的。
c:takeUntil:(RACSignal *)
- (RACSignal *)rac_textSignal { @weakify(self); return [[[[[RACSignal concat:[self rac_signalForControlEvents:UIControlEventEditingChanged]] map:^(UITextField *x) { return x.text; }] takeUntil:self.rac_willDeallocSignal] // bingo!}
當給定的signal完成前一直取值,也就是這個Signal一直到textField執行dealloc
時才停止
d:takeUntilBlock:(BOOL (^)(id x))
[[self.inputTextField.rac_textSignal takeUntilBlock:^BOOL(NSString *value) { return [value isEqualToString:@"stop"];}] subscribeNext:^(NSString *value) { NSLog(@"current value is not `stop`: %@", value);}];
對于每個next值,運行block,當block返回YES時停止取值
e:takeWhileBlock:(BOOL (^)(id x))
上面的反向邏輯,對于每個next值,block返回 YES時才取值
f:skip:(NSUInteger)
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"1"]; [subscriber sendNext:@"2"]; [subscriber sendNext:@"3"]; [subscriber sendCompleted]; return nil;}] skip:1] subscribeNext:^(id x) { NSLog(@"only 2 and 3 will be print: %@", x);}];
從開始跳過N次的next值
g:skipUntilBlock:(BOOL (^)(id x))
和- takeUntilBlock:
同理,一直跳,直到block為YES
h:skipWhileBlock:(BOOL (^)(id x))
和- takeWhileBlock:
同理,一直跳,直到block為NO
16:創建信號
RACSignal *nameSignal=[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"www.companysz.com/wujy"]; [subscriber sendCompleted]; return nil; }]; [nameSignal subscribeNext:^(id x) { NSLog(@"當前輸入的值為%@",x); }];
另外一種比較完整的寫法:
-(RACSignal *)urlResults { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSError *error; NSString *result = [NSString stringWithContentsOfURL:[NSURL URLWithString:@"http://www.devtang.com"] encoding:NSUTF8StringEncoding error:&error]; NSLog(@"download"); if (!result) { [subscriber sendError:error]; } else { [subscriber sendNext:result]; [subscriber sendCompleted]; } return [RACDisposable disposableWithBlock:^{ NSLog(@"clean up"); }]; }];}
如果還沒有被Next時它就是冷信號,只有被調用時才是熱信號;
說明:Signal and Subscriber是RAC最核心的內容,這里我想用插頭和插座來描述,插座是Signal,插頭是Subscriber。想象某個遙遠的星球,他們的電像某種物質一樣被集中存儲,且很珍貴。插座負責去獲取電,插頭負責使用電,而且一個插座可以插任意數量的插頭。當一個插座(Signal)沒有插頭(Subscriber)時什么也不干,也就是處于冷(Cold)的狀態,只有插了插頭時才會去獲取,這個時候就處于熱(Hot)的狀態
17:UI擴展
UIAlertView *alertView =[[UIAlertView alloc]initWithTitle:@"" message:@"" delegate:nil cancelButtonTitle:@"A" otherButtonTitles:@"B",@"C", nil]; [[alertView rac_buttonClickedSignal] subscribeNext:^(id x) { NSLog(@"%@",x); }]; [alertView show];
RAC在很多UI控件里已經擴展出一些可以用的內容,例如上面的代碼;
一張不錯的RAC類圖:
不錯的文章推薦:
這樣好用的ReactiveCocoa,根本停不下來 http://www.cocoachina.com/ios/20150817/13071.html
ReactiveCocoa基本組件:深入淺出RACCommand http://www.tuicool.com/articles/nYJRvu
ReactiveCocoa 和 MVVM 入門 http://www.cocoachina.com/ios/20150526/11930.html
ReactiveCocoa自述:工作原理和應用 http://www.cocoachina.com/ios/20150702/12302.html
RACSignal的巧克力工廠 http://www.companysz.com/sunnyxx/p/3547763.html
ReactiveCocoa一些概念講解 http://www.thinksaas.cn/group/topic/347067/
細說ReactiveCocoa的冷信號與熱信號(二):為什么要區分冷熱信號 http://www.tuicool.com/articles/e2uMzyq
細說ReactiveCocoa的冷信號與熱信號(三):怎么處理冷信號與熱信號 http://www.tuicool.com/articles/emIVZjY
新聞熱點
疑難解答