一诱告、概述
ReactiveCocoa簡(jiǎn)稱RAC精居,基于響應(yīng)式編程思想的Objective-C實(shí)踐靴姿。本文主要結(jié)合代碼示例列舉了RAC的常用類佛吓,常用方法和常用宏辈毯,幫助開發(fā)者快速上手怎樣使用RAC谆沃。
二唁影、常用的類
1据沈、RACSiganl:信號(hào)類锌介,用于傳遞改變的數(shù)據(jù)孔祸,可傳遞以下三種狀態(tài):
- sendNext:可理解為傳遞正確數(shù)據(jù),告訴訂閱者進(jìn)行下一步處理
- sendError:傳遞的數(shù)據(jù)錯(cuò)誤拂蝎,告訴訂閱者錯(cuò)誤處理
- sendCompleted:告訴訂閱者已完成
2温自、RACSubscriber:表示訂閱者的意思悼泌,用于發(fā)送信號(hào)券躁,這是一個(gè)協(xié)議也拜,不是一個(gè)類慢哈,只要遵守這個(gè)協(xié)議卵贱,并且實(shí)現(xiàn)方法才能成為訂閱者。通過(guò)create創(chuàng)建的信號(hào)侣集,都有一個(gè)訂閱者键俱,幫助他發(fā)送數(shù)據(jù)。
-
沒有訂閱者的 RACSiganl 就是冷信號(hào)世分,相反有訂閱者的 RACSiganl 就是熱信號(hào)编振。為了更好地理解,請(qǐng)參考下圖:
RAC.png
下面舉個(gè)RAC簡(jiǎn)單用法的栗子:
//信號(hào)
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"666666"]; //發(fā)送正確數(shù)據(jù)
// [subscriber sendError:[NSError errorWithDomain:@"xxx" code:1001 userInfo:@{@"errorMessage" : @"錯(cuò)誤"}]]; //發(fā)送錯(cuò)誤
// [subscriber sendCompleted]; //完成
return nil;
}];
//訂閱者
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"--%@--",x); //接收到數(shù)據(jù)
} error:^(NSError * _Nullable error) {
NSLog(@"--%@--",error); //出錯(cuò)處理
} completed:^{
NSLog(@"完成"); // 信號(hào)完成
}];
createSignal 方法block中對(duì)應(yīng)的就是上面說(shuō)的 RACSiganl 傳遞的三種狀態(tài)(sendNext, sendError, sendCompleted)臭埋,subscribeNext就是訂閱方法踪央,對(duì)應(yīng)著 RACSiganl 發(fā)出的三種狀態(tài)做出不同的處理瓢阴。
3畅蹂、RACSubject:既可以充當(dāng)信號(hào),也可以發(fā)送信號(hào)
其實(shí)用法和上面栗子類似:
RACSubject *subj = [RACSubject subject];
//訂閱信號(hào)
[subj subscribeNext:^(id _Nullable x) {
NSLog(@"--%@--",x); //接收到數(shù)據(jù)
} error:^(NSError * _Nullable error) {
NSLog(@"--%@--",error); //出錯(cuò)處理
} completed:^{
NSLog(@"完成"); // 信號(hào)完成
}];
//發(fā)送信號(hào)
[subj sendNext:@"666666"]; //發(fā)送正確數(shù)據(jù)
// [subscriber sendError:[NSError errorWithDomain:@"xxx" code:1001 userInfo:@{@"errorMessage" : @"錯(cuò)誤"}]]; //發(fā)送錯(cuò)誤
// [subscriber sendCompleted]; //完成
RACSubject的作用通常用來(lái)代替代理荣恐。下面上代碼:
- 首先創(chuàng)建一個(gè)RedView的類繼承于UIView
RedView.h
#import <UIKit/UIKit.h>
#import <ReactiveObjC/ReactiveObjC.h>
@interface RedView : UIView
@property (nonatomic,strong) RACSubject *subject;
@end
RedView.m
#import "RedView.h"
@implementation RedView
- (RACSubject *)subject {
if (_subject == nil) {
_subject = [RACSubject subject];
}
return _subject;
}
- (IBAction)btnClick:(UIButton *)sender {
[self.subject sendNext:@"777777"];
}
@end
控制器中代碼:
#import "OrderController.h"
#import <ReactiveObjC/ReactiveObjC.h>
#import "RedView.h"
@interface OrderController ()
@property (weak, nonatomic) IBOutlet RedView *redView;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
[self.redView.subject subscribeNext:^(id _Nullable x) {
NSLog(@"--%@--",x); //接收到數(shù)據(jù)
}];
}
這樣就完成了和代理一樣的功能
4液斜、RACMulticastConnection:信號(hào)連接類
一般情況下,信號(hào)被訂閱多少次叠穆,信號(hào)創(chuàng)建是的block就調(diào)用多少次少漆。但是實(shí)際開發(fā)中,block只需調(diào)用一次就可以了痹束。RACMulticastConnection可以實(shí)現(xiàn)不管訂閱多少次信號(hào)检疫,信號(hào)的block只請(qǐng)求一次:
- (void)viewDidLoad {
[super viewDidLoad];
//不管訂閱多少次信號(hào),只請(qǐng)求一次
//1.創(chuàng)建信號(hào)
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"請(qǐng)求數(shù)據(jù)");
[subscriber sendNext:@"請(qǐng)求數(shù)據(jù):777"];
return nil;
}];
//2.把信號(hào)轉(zhuǎn)換成連接類
RACMulticastConnection * connection = [signal publish];
//3.訂閱連接類信號(hào)
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"訂閱1:%@",x);
}];
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"訂閱2:%@",x);
}];
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"訂閱3:%@",x);
}];
//4.連接
[connection connect];
}
上面的例子中信號(hào)被訂閱了3次祷嘶,但是請(qǐng)求數(shù)據(jù)只請(qǐng)求了1次屎媳。
5、RACCommand:處理事件的類
- (void)viewDidLoad {
[super viewDidLoad];
///1.創(chuàng)建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
/*******此block執(zhí)行命令時(shí)調(diào)用*******/
//input:對(duì)應(yīng)執(zhí)行命令的參數(shù)论巍,拿到參數(shù)后可以寫處理事件代碼
NSLog(@"input:%@",input);
//不能返回空的信號(hào)
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//傳遞事件產(chǎn)生的數(shù)據(jù)
[subscriber sendNext:@"執(zhí)行命令產(chǎn)生的數(shù)據(jù)"];
return nil;
}];
}];
///2.訂閱命令內(nèi)部的信號(hào)
//方式1:
//1.執(zhí)行命令
RACSignal *signal = [command execute:@"666"];
//2.訂閱信號(hào)
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"~%@~",x);
}];
//方式2:(注意:必須在執(zhí)行命令前訂閱信號(hào))
//1.訂閱信號(hào)
//executionSignals:信號(hào)源烛谊,信號(hào)中的信號(hào)
[command.executionSignals subscribeNext:^(id _Nullable x) {
[x subscribeNext:^(id _Nullable x) {
NSLog(@"~%@~",x);
}];
}];
//2.執(zhí)行命令
[command execute:@"777"];
///3.監(jiān)聽事件有沒有完成
[command.executing subscribeNext:^(NSNumber * _Nullable x) {
if ([x boolValue] == YES) {
NSLog(@"正在執(zhí)行");
}else {
NSLog(@"執(zhí)行完成/沒有執(zhí)行");
}
}];
}
上面的例子例舉了創(chuàng)建command對(duì)象block中,事件如何處理嘉汰,事件產(chǎn)生的數(shù)據(jù)如何傳遞丹禀。command.executing監(jiān)控事件執(zhí)行的過(guò)程。
三、常用的方法
- 監(jiān)聽某個(gè)對(duì)象是否調(diào)用某個(gè)方法:
#import "OrderController.h"
#import <ReactiveObjC/ReactiveObjC.h>
#import "RedView.h"
#import "HomeController.h"
@interface OrderController ()
@property (weak, nonatomic) IBOutlet RedView *redView;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
[[self rac_signalForSelector:@selector(push:)] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"跳轉(zhuǎn)");
}];
}
- (IBAction)push:(id)sender {
HomeController *vc = [[HomeController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
當(dāng)點(diǎn)擊按鈕調(diào)用 push: 方法時(shí)就會(huì)監(jiān)聽到 push: 方法被調(diào)用了双泪,從而打印出 “跳轉(zhuǎn)”
- 代替KVO:
#import "OrderController.h"
#import <ReactiveObjC/ReactiveObjC.h>
#import "RedView.h"
@interface OrderController ()
@property (weak, nonatomic) IBOutlet RedView *redView;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
[[_redView rac_valuesForKeyPath:@"frame" observer:self] subscribeNext:^(id _Nullable x) {
//修改的值
NSLog(@"%@",x);
}];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
_redView.frame = CGRectMake(50, 50, 200, 200);
}
上述例子同KVO功能一樣持搜,監(jiān)聽了_redView對(duì)象對(duì)象中的frame屬性,當(dāng)點(diǎn)擊屏幕調(diào)用touchesBegan改變_redView的frame時(shí)焙矛,block里的方法會(huì)被調(diào)用葫盼。
- 監(jiān)聽事件
[[_btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"按鈕被點(diǎn)擊了");
}];
給按鈕添加點(diǎn)擊事件
- 代替通知
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSLog(@"鍵盤被彈出");
}];
實(shí)現(xiàn)監(jiān)聽鍵盤彈出通知
- 監(jiān)聽文本框變化
[[self.textField rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"UITextField:%@",x);
}];
- 所有信號(hào)都返回結(jié)果的時(shí)候,統(tǒng)一做處理
- (void)viewDidLoad {
[super viewDidLoad];
//信號(hào)一
RACSignal *sg1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//模擬從服務(wù)器請(qǐng)求數(shù)據(jù)
[subscriber sendNext:@"請(qǐng)求的數(shù)據(jù)1"]; //請(qǐng)求成功后發(fā)送數(shù)據(jù)
return nil;
}];
//信號(hào)二
RACSignal *sg2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//模擬從服務(wù)器請(qǐng)求數(shù)據(jù)
[subscriber sendNext:@"請(qǐng)求的數(shù)據(jù)2"]; //請(qǐng)求成功后發(fā)送數(shù)據(jù)
return nil;
}];
//方法upDateUI的參數(shù)村斟,對(duì)應(yīng)每個(gè)信號(hào)發(fā)送的數(shù)據(jù)
[self rac_liftSelector:@selector(upDateUIWithData1:data2:) withSignalsFromArray:@[sg1,sg2]];
}
- (void)upDateUIWithData1:(NSString *)data1 data2:(NSString *)data2{
//拿到請(qǐng)求數(shù)據(jù)
NSLog(@"更新UI:%@--%@",data1,data2);
}
四贫导、常用的宏
- RAC(xx,xx):給某個(gè)對(duì)象的某個(gè)屬性綁定信號(hào),只要產(chǎn)生信號(hào)內(nèi)容蟆盹,就會(huì)把內(nèi)容給屬性賦值
@interface OrderController ()
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) IBOutlet UILabel *label;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
//方法一孩灯,普通方法實(shí)現(xiàn)
@weakify(self)
[_textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
@strongify(self)
self.label.text = x;
}];
//方法二,宏方法實(shí)現(xiàn)
RAC(_label,text) = _textField.rac_textSignal;
}
上面例子中方法二是利用宏實(shí)現(xiàn)的逾滥,首先將_label的text屬性綁定了_textField的信號(hào)峰档,只要_textField內(nèi)容發(fā)生改變,就會(huì)賦值給_label的text屬性匣距。 例子中兩種方法實(shí)現(xiàn)的效果一樣面哥,用RAC()宏寫更簡(jiǎn)便。
- RACObserve(_redView, frame):只要對(duì)象屬性改變就會(huì)產(chǎn)生信號(hào)
@interface OrderController ()
@property (weak, nonatomic) IBOutlet RedView *redView;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
[RACObserve(_redView, frame) subscribeNext:^(id _Nullable x) {
//修改的致
NSLog(@"%@",x);
}];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
_redView.frame = CGRectMake(50, 50, 200, 200);
}
與上面說(shuō)的代 替KVO: 舉的例子實(shí)現(xiàn)效果一樣
- @weakify(self)毅待,@strongify(self):解決RAC中的循環(huán)引用
@weakify(self)
[_textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
@strongify(self)
self.label.text = x;
}];
在block外加@weakify(self)方法尚卫,block里面加 @strongify(self)方法,之后只用self.就不會(huì)產(chǎn)生循環(huán)引用尸红。
五吱涉、RAC高級(jí)操作方法
- bind
具體操作:給RAC的信號(hào)進(jìn)行綁定,只要信號(hào)已發(fā)送數(shù)據(jù)外里,就能監(jiān)聽到怎爵,從而把數(shù)據(jù)改成自己想要的
- (void)viewDidLoad {
[super viewDidLoad];
//1.創(chuàng)建信號(hào)
RACSubject *subj = [RACSubject subject];
//2.綁定信號(hào)
RACSignal *bindSignal = [subj bind:^RACSignalBindBlock _Nonnull{
return ^RACSignal *(id value, BOOL *stop) {
//只要源信號(hào)發(fā)送數(shù)據(jù),就會(huì)調(diào)用此block
//此block作用:處理源信號(hào)內(nèi)容
//value:信號(hào)源發(fā)送內(nèi)容
value = [NSString stringWithFormat:@"orange%@",value];
NSLog(@"修改后的value:%@",value);
return [RACReturnSignal return:value];
};
}];
//3.訂閱綁定信號(hào)
[bindSignal subscribeNext:^(id _Nullable x) {
NSLog(@"接收到綁定信號(hào)處理完的信號(hào):%@",x);
}];
//4.發(fā)送數(shù)據(jù)
[subj sendNext:@"888888"];
}
上述例子中盅蝗,發(fā)送的信號(hào)為“888888”鳖链,通過(guò)綁定信號(hào),對(duì)源信號(hào)數(shù)據(jù)進(jìn)行修改墩莫,訂閱后的信號(hào)內(nèi)容就被修改了芙委。
- 映射
映射可以把源信號(hào)內(nèi)容更改成新的內(nèi)容
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建信號(hào)
RACSubject *subj = [RACSubject subject];
//綁定信號(hào)
RACSignal *bindSignalX = [subj map:^id _Nullable(id _Nullable value) {
//返回的類型就是你需要映射的值
return [NSString stringWithFormat:@"%d",[value intValue] + 1];
}];
//訂閱信號(hào)
[bindSignalX subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
//發(fā)送數(shù)據(jù)
[subj sendNext:@"666666"];
}
上述例子中,綁定信號(hào)block中狂秦,將value值+1灌侣,直接return的值就是要映射的值,所以在訂閱信號(hào)中的內(nèi)容就在發(fā)送數(shù)據(jù)基礎(chǔ)上+1裂问。
- 壓縮信號(hào):所有信號(hào)都發(fā)送內(nèi)容時(shí)侧啼,才會(huì)調(diào)用
使用場(chǎng)景:當(dāng)一個(gè)界面多個(gè)請(qǐng)求時(shí)牛柒,要等所有請(qǐng)求完成更新UI
- (void)viewDidLoad {
[super viewDidLoad];
RACSubject *s1 = [RACSubject subject];
RACSubject *s2 = [RACSubject subject];
//壓縮成一個(gè)信號(hào)
RACSignal *yaS = [s1 zipWith:s2];
//訂閱信號(hào)
[yaS subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
//發(fā)送信號(hào)
[s1 sendNext:@"1"];
[s2 sendNext:@"2"];
}
上面例子中,當(dāng)s1和s2同時(shí)發(fā)送信號(hào)時(shí)痊乾,訂閱信號(hào)的block才會(huì)調(diào)用皮壁。
- 組合信號(hào):將兩個(gè)信號(hào)組合,并且可以同時(shí)處理兩個(gè)信號(hào)的內(nèi)容
使用場(chǎng)景:登錄頁(yè)面賬號(hào)輸入框和密碼輸入框同時(shí)輸入內(nèi)容時(shí)符喝,登錄按鈕才能點(diǎn)擊
@interface HomeController ()
@property (weak, nonatomic) IBOutlet UIButton *btn;
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) IBOutlet UITextField *textField1;
@end
@implementation HomeController
- (void)viewDidLoad {
[super viewDidLoad];
@weakify(self)
//reduce:聚合的意思,合并兩個(gè)信號(hào)數(shù)據(jù)闪彼,進(jìn)行匯總計(jì)算時(shí)使用的
RACSignal *signal = [RACSignal combineLatest:@[self.textField.rac_textSignal, self.textField1.rac_textSignal] reduce:^id (NSString *text, NSString *text1){
//判斷輸入框位數(shù)(需轉(zhuǎn)成NSNumber類型)
BOOL isHidden;
if (text.length >= 3 && text1.length >= 6) {
isHidden = YES;
}else {
isHidden = NO;
}
return @(isHidden);
}];
//訂閱信號(hào)
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"達(dá)成條件返回結(jié)果:%@",x);
@strongify(self)
self.btn.hidden = ![x boolValue];
}];
}
上面例子中甜孤,combineLatest:方法組合了textField和textField1的信號(hào)协饲,reduce的block中是對(duì)兩個(gè)組合信號(hào)的數(shù)據(jù)進(jìn)行計(jì)算并返回計(jì)算后的結(jié)果。通過(guò)訂閱組合后的信號(hào)缴川,就能拿到reduce處理后的解果茉稠,來(lái)判斷登錄按鈕是否顯示。
- 過(guò)濾
例1:過(guò)濾掉一些內(nèi)容
[[_textField.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
//value源信號(hào)內(nèi)容
//返回值為過(guò)濾的條件
return [value length] > 5 ; //只有當(dāng)文本框內(nèi)容大于5才能獲取文本框內(nèi)容
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"大于5時(shí)打印內(nèi)容:%@",x);
}];
第一個(gè)block中直接返回想要過(guò)濾的條件把夸,只有條件達(dá)成時(shí)而线,訂閱信號(hào)subscribeNext的block內(nèi)的方法才執(zhí)行。
例2:忽略某個(gè)值
RACSubject *igSub = [RACSubject subject];
RACSignal *igSignal = [igSub ignore:@"1"];
[igSignal subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[igSub sendNext:@"1"];
利用ignore方法設(shè)置要忽略的內(nèi)容:“1”恋日,當(dāng)發(fā)送的消息(sendNext)為“1”時(shí)膀篮,訂閱消息時(shí)就接收不到了。
例3:當(dāng)前值和上一個(gè)值相同就不會(huì)被訂閱
使用場(chǎng)景:當(dāng)模型里一個(gè)屬性沒有變就不會(huì)更新UI界面岂膳,只有不同時(shí)才會(huì)刷新UI
RACSubject *disSub = [RACSubject subject];
[[disSub distinctUntilChanged] subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[disSub sendNext:@"1"];
[disSub sendNext:@"1"];
兩次發(fā)送的都是“1”誓竿,但是block中只執(zhí)行了一次。
- 標(biāo)記
takeUntil需要一個(gè)信號(hào)作為標(biāo)記谈截,當(dāng)標(biāo)記的信號(hào)發(fā)送數(shù)據(jù)筷屡,就停止。
RACSubject * subject = [RACSubject subject];
RACSubject * subject1 = [RACSubject subject];
[[subject takeUntil:subject1] subscribeNext:^(id _Nullable x) {
NSLog(@"---%@---",x);
}];
[subject1 subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
[subject1 sendNext:@"Stop"];
[subject sendNext:@"4"];
[subject sendNext:@"5"];
上面例子中subject1信號(hào)發(fā)送數(shù)據(jù)之后簸喂,subject就接收不到4和5了毙死。
- rac_willDeallocSignal
對(duì)象銷毀時(shí)發(fā)動(dòng)的信號(hào)
// 監(jiān)聽文本框的改變,直到當(dāng)前對(duì)象被銷毀
[_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
以上例子中喻鳄,當(dāng)前對(duì)象被銷毀時(shí)扼倘,會(huì)發(fā)送rac_willDeallocSignal信號(hào),rac_textSignal就停止監(jiān)聽了除呵。