ReactiveObjC看這裡就夠了
1、什麼是ReactiveObjC
系列的一個OC方面用得很多的響應式程式設計三方框架,其Swift方面的框架是(ReactiveSwift )。RAC用訊號(類名為RACSignal)來代替和處理各種變數的變化和傳遞。核心思路:建立訊號->訂閱訊號(subscribeNext)->傳送訊號
通過訊號signals的傳輸,重新組合和響應,軟體程式碼的編寫邏輯思路將變得更清晰緊湊,有條理,而不再需要對變數的變化不斷的觀察更新。
2、什麼是函式響應式程式設計
響應式程式設計是一種和事件流有關的程式設計模式,關注導致狀態值改變的改變的行為事件,一系列事件組成了事件流,一系列事件是導致屬性值發生變化的原因,非常類似於設計模式中的觀察者模式。在網上流傳一個非常經典的解釋響應式程式設計的概念,在一般的程式開發中:a = b + c,賦值之後 b 或者 c 的值變化後,a 的值不會跟著變化,而響應式程式設計的目標就是,如果 b 或者 c 的數值發生變化,a 的數值會同時發生變化;
3、ReactiveObjC的流程分析
ReactiveObjC主要有三個關鍵類:
1、RACSignal
訊號
RACSignal
是各種訊號的基類,其中RACDynamicSignal
是用的最多的動態訊號
2、RACSubscriber
訂閱者
RACSubscriber
是實現了RACSubscriber
協議的訂閱者類,這個協議定義了4個必須實現的方法
@protocol RACSubscriber <NSObject> @required - (void)sendNext:(nullable id)value;//常見 - (void)sendError:(nullable NSError *)error;//常見 - (void)sendCompleted;//常見 - (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable; @end
RACSubscriber
主要儲存了三個block,跟三個常見的協議方法一一對應
@property (nonatomic, copy) void (^next)(id value); @property (nonatomic, copy) void (^error)(NSError *error); @property (nonatomic, copy) void (^completed)(void);
3、RACDisposable
清潔工
RACDisposable
主要是對資源的釋放處理,其中使用RACDynamicSignal
時,會建立一個RACCompoundDisposable
管理清潔工物件。其內部定義了兩個陣列,一個是_inlineDisposables[2]
固定長度2的A fast array
,超出2個物件的長度由_disposables
陣列管理,_inlineDisposables
陣列速度快,兩個陣列都是執行緒安全的。
4、ReactiveObjC匯入工程的方式
pod 'ReactiveObjC'
5、ReactiveObjC的幾種使用情況
-
NSArray
陣列遍歷
NSArray * array = @[@"1",@"2",@"3",@"4",@"5",@"6"]; [array.rac_sequence.signal subscribeNext:^(id_Nullable x) { NSLog(@"陣列內容:%@", x); }];
-
NSArray
快速替換陣列中內容為99和單個替換陣列內容,兩個方法都不會改變原陣列內容,操作完後都會生成一個新的陣列,省去了建立可變陣列然後遍歷出來單個新增的步驟。
NSArray * array = @[@"1",@"2",@"3",@"4",@"5",@"6"]; /* NSArray * newArray = [[array.rac_sequence mapReplace:@"99"] array]; NSLog(@"%@",newArray); */ NSArray * newArray = [[array.rac_sequence map:^id _Nullable(id_Nullable value) { NSLog(@"原陣列內容%@",value); return @"99"; }] array]; NSLog(@"%@",newArray);
-
NSDictionary
字典遍歷
NSDictionary * dic = @{@"name":@"Tom",@"age":@"20"}; [dic.rac_sequence.signal subscribeNext:^(id_Nullable x) { RACTupleUnpack(NSString *key, NSString * value) = x;//X為為一個元祖,RACTupleUnpack能夠將key和value區分開 NSLog(@"陣列內容:%@--%@",key,value); }];
-
UIButton
監聽按鈕的點選事件
UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom]; btn.frame = CGRectMake(100, 200, 100, 60); btn.backgroundColor = [UIColor blueColor]; [self.view addSubview:btn]; //監聽點選事件 [[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) { NSLog(@"%@",x);//x為一個button物件,別看型別為UIControl,繼承關係UIButton-->UIControl-->UIView-->UIResponder-->NSObject }];
-
UITextField
監聽輸入框的一些事件
UITextField * textF = [[UITextField alloc]initWithFrame:CGRectMake(100, 100, 200, 40)]; textF.placeholder = @"請輸入內容"; textF.textColor = [UIColor blackColor]; [self.view addSubview:textF]; //實時監聽輸入框中文字的變化 [[textF rac_textSignal] subscribeNext:^(NSString * _Nullable x) { NSLog(@"輸入框的內容--%@",x); }]; //UITextField的UIControlEventEditingChanged事件,免去了KVO [[textF rac_signalForControlEvents:UIControlEventEditingChanged] subscribeNext:^(__kindof UIControl * _Nullable x) { NSLog(@"%@",x); }]; //新增監聽條件 [[textF.rac_textSignal filter:^BOOL(NSString * _Nullable value) { return [value isEqualToString:@"100"];//此處為判斷條件,當輸入的字元為100的時候執行下面方法 }]subscribeNext:^(NSString * _Nullable x) { NSLog(@"輸入框的內容為%@",x); }];
-
KVO
代替KVO來監聽按鈕frame的改變
UIButton * loginBtn = [UIButton buttonWithType:UIButtonTypeCustom]; loginBtn.frame = CGRectMake(100, 210, 100, 60); loginBtn.backgroundColor = [UIColor blueColor]; [loginBtn setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; [loginBtn setTitle:@"666" forState:UIControlStateNormal]; //[loginBtn setTitle:@"111" forState:UIControlStateDisabled]; [self.view addSubview:loginBtn]; //監聽點選事件 [[loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) { NSLog(@"%@",x);//x為一個button物件,別看型別為UIControl,繼承關係UIButton-->UIControl-->UIView-->UIResponder-->NSObject x.frame = CGRectMake(100, 210, 200, 300); }]; //KVO監聽按鈕frame的改變 [[loginBtn rac_valuesAndChangesForKeyPath:@"frame" options:(NSKeyValueObservingOptionNew) observer:self] subscribeNext:^(RACTwoTuple<id,NSDictionary *> * _Nullable x) { NSLog(@"frame改變了%@",x); }]; //下面方法也能監聽,但是在按鈕建立的時候此方法也執行了,簡單說就是在介面展示之前此方法就走了一遍,總感覺怪怪的。 /* [RACObserve(loginBtn, frame) subscribeNext:^(id_Nullable x) { NSLog(@"frame改變了%@",x); }];
-
NSNotification
監聽通知事件
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardDidShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) { NSLog(@"監聽鍵盤彈出"); //不知道為啥此方法不止走一次,但是原本的通知監聽方法只走一次,有知道的可以私信我,謝謝 }];
-
timer
代替timer定時迴圈執行方法
[[RACSignal interval:2.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) { //這裡面的方法2秒一迴圈 }]; //如果關閉定時器,停止需要建立一個全域性的disposable //@property (nonatomic, strong) RACDisposable * disposable;//建立 /* self.disposable = [[RACSignal interval:2.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) { NSLog(@"當前時間:%@", x); // x 是當前的系統時間 //關閉計時器 [self.disposable dispose]; }]; */
6、開發中用到的小栗子
- 傳送簡訊驗證碼的按鈕倒計時
/* @property (nonatomic, strong) RACDisposable * disposable; @property (nonatomic, assign) NSInteger time; */ //上面兩句要提前定義 UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 200, 200, 50)]; btn.titleLabel.textAlignment = NSTextAlignmentCenter; btn.backgroundColor = [UIColor greenColor]; [btn setTitle:@"傳送驗證碼" forState:(UIControlStateNormal)]; [self.view addSubview:btn]; [[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) { self.time = 10; btn.enabled = NO; [btn setTitle:[NSString stringWithFormat:@"請稍等%zd秒",self.time] forState:UIControlStateDisabled]; self.disposable = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) { //減去時間 self.time --; //設定文字 NSString *text = (self.time > 0) ? [NSString stringWithFormat:@"請稍等%zd秒",_time] : @"重新發送"; if (self.time > 0) { btn.enabled = NO; [btn setTitle:text forState:UIControlStateDisabled]; }else{ btn.enabled = YES; [btn setTitle:text forState:UIControlStateNormal]; //關掉訊號 [self.disposable dispose]; } }]; }];
- 登入按鈕的狀態根據賬號和密碼輸入框內容的長度來改變
UITextField *userNameTF = [[UITextField alloc]initWithFrame:CGRectMake(100, 70, 200, 50)]; UITextField *passwordTF = [[UITextField alloc]initWithFrame:CGRectMake(100, 130, 200, 50)]; userNameTF.placeholder = @"請輸入使用者名稱"; passwordTF.placeholder = @"請輸入密碼"; [self.view addSubview:userNameTF]; [self.view addSubview:passwordTF]; UIButton *loginBtn = [[UIButton alloc]initWithFrame:CGRectMake(40, 180, 200, 50)]; [loginBtn setTitle:@"馬上登入" forState:UIControlStateNormal]; [self.view addSubview:loginBtn]; //根據textfield的內容來改變登入按鈕的點選可否 RAC(loginBtn, enabled) = [RACSignal combineLatest:@[userNameTF.rac_textSignal, passwordTF.rac_textSignal] reduce:^id _Nullable(NSString * username, NSString * password){ return @(username.length >= 11 && password.length >= 6); }]; //根據textfield的內容來改變登入按鈕的背景色 RAC(loginBtn, backgroundColor) = [RACSignal combineLatest:@[userNameTF.rac_textSignal, passwordTF.rac_textSignal] reduce:^id _Nullable(NSString * username, NSString * password){ return (username.length >= 11 && password.length >= 6) ? [UIColor redColor] : [UIColor grayColor]; }];
7、文章會持續更新,鄙人這裡也只是列出來了常用的方法,一些更深的更簡潔的方法還需要點進去看原始碼,喜歡的話就關注下,謝謝。
結尾:
本文參考:
關於ReactiveObjC原理及流程簡介https://www.jianshu.com/p/fecbe23d45c1
響應式程式設計之ReactiveObjC常見用法https://www.jianshu.com/p/6af75a449d90
【iOS 開發】ReactiveObjC(RAC)的使用匯總
https://www.jianshu.com/p/0845b1a07bfa