參考資料:《神奇的RAC宏》
本文知識(shí)點(diǎn):RACMulticastConnection(避免多次請(qǐng)求)、RACCommand(網(wǎng)絡(luò)請(qǐng)求,監(jiān)聽(tīng)命令狀態(tài)厕九,區(qū)別與RACSubject)、RAC常用宏地回。
1. RACMulticastConnection
- 當(dāng)一個(gè)信號(hào)扁远,被多次訂閱時(shí),為了保證創(chuàng)建信號(hào)時(shí)刻像,避免多次調(diào)用創(chuàng)建信號(hào)中的block畅买,造成副作用,可以使用這個(gè)類(lèi)處理.
- 注意:
RACMulticastConnection
通過(guò)RACSignal
的-publish
或者-muticast:
方法創(chuàng)建细睡。
1.1 使用步驟
- 創(chuàng)建信號(hào)
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
- 創(chuàng)建連接
RACMulticastConnection *connect = [signal publish];
- 訂閱信號(hào),注意:訂閱的不再是之前的信號(hào)谷羞,而是connect的信號(hào)。
[connect.signal subscribeNext:nextBlock]
- 連接
[connect connect]
1.2 底層原理
- 創(chuàng)建
connect
溜徙,connect.sourceSignal -> RACSignal
(原始信號(hào))connect.signal -> RACSubject
- 訂閱
connect.signal
湃缎,會(huì)調(diào)用RACSubjec
t的subscribeNext
,創(chuàng)建訂閱者蠢壹,而且把訂閱者保存起來(lái)嗓违,不會(huì)執(zhí)行block
。 -
[connect connect]
內(nèi)部會(huì)訂閱RACSignal
(原始信號(hào))图贸,并且訂閱者是RACSubject
1> 訂閱原始信號(hào)蹂季,就會(huì)調(diào)用原始信號(hào)中的didSubscribe
2>didSubscribe
冕广,拿到訂閱者調(diào)用sendNext
,其實(shí)是調(diào)用RACSubjec
t的sendNext
-
RACSubject
的sendNext
,會(huì)遍歷RACSubject
所有訂閱者發(fā)送信號(hào)偿洁。
1> 因?yàn)閯倓偟诙郊岩ぃ际窃谟嗛?code>RACSubject,因此會(huì)拿到第二步所有的訂閱者父能,調(diào)用他們的nextBlock
1.3 應(yīng)用場(chǎng)景:避免多次請(qǐng)求數(shù)據(jù)
需求:假設(shè)在一個(gè)信號(hào)中發(fā)送請(qǐng)求神凑,每次訂閱一次都會(huì)發(fā)送請(qǐng)求,這樣就會(huì)導(dǎo)致多次請(qǐng)求何吝。
解決:使用RACMulticastConnection
就能解決溉委。
// 使用RACSignal出現(xiàn)的情況:多次執(zhí)行nextBlock
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
static int i = 0;
i++;
[subscriber sendNext:@(i)];
return nil;
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"x = %@", x);
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"x = %@", x);
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"x = %@", x);
}];
Output:
2019-06-14 15:24:32.321216+0800 RacDemo[7221:2430572] x = 1
2019-06-14 15:24:32.321341+0800 RacDemo[7221:2430572] x = 2
2019-06-14 15:24:32.321483+0800 RacDemo[7221:2430572] x = 3
// 解決重復(fù)請(qǐng)求問(wèn)題使用RACMulticastConnection處理:
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
static int i = 0;
i++;
[subscriber sendNext:@(i)];
return nil;
}];
//創(chuàng)建連接
RACMulticastConnection *connect = [signal publish];
//訂閱信號(hào)
//注意:訂閱信號(hào),也不能激活信號(hào)爱榕,只是保存訂閱者到數(shù)組瓣喊,必須通過(guò)連接,當(dāng)調(diào)用連接,就會(huì)一次性調(diào)用所有訂閱者的sendNext:
[connect.signal subscribeNext:^(id _Nullable x) {
NSLog(@"x = %@", x);
}];
[connect.signal subscribeNext:^(id _Nullable x) {
NSLog(@"x = %@", x);
}];
[connect.signal subscribeNext:^(id _Nullable x) {
NSLog(@"x = %@", x);
}];
//連接,激活信號(hào)(須寫(xiě)在最后)
[connect connect];
Output:
2019-06-14 15:28:44.720282+0800 RacDemo[7224:2431304] x = 1
2019-06-14 15:28:44.720343+0800 RacDemo[7224:2431304] x = 1
2019-06-14 15:28:44.720361+0800 RacDemo[7224:2431304] x = 1
2. RACCommand
- RAC中用于處理事件的類(lèi)黔酥,可以把事件如何處理,事件中的數(shù)據(jù)如何傳遞藻三,包裝到這個(gè)類(lèi)中,他可以很方便的監(jiān)控事件的執(zhí)行過(guò)程跪者。
- 監(jiān)聽(tīng)按鈕點(diǎn)擊棵帽,網(wǎng)絡(luò)請(qǐng)求。
2.1 使用步驟
- 創(chuàng)建命令
initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
- 在
signalBlock
中渣玲,創(chuàng)建RACSignal
逗概,并且作為signalBlock
的返回值 - 執(zhí)行命令
- (RACSignal *)execute:(id)input
使用注意:
signalBlock
必須要返回一個(gè)信號(hào),不能傳nil
.- 如果不想要傳遞信號(hào)忘衍,直接創(chuàng)建空的信號(hào)
[RACSignal empty];
RACCommand
中信號(hào)如果數(shù)據(jù)傳遞完逾苫,必須調(diào)用[subscriber sendCompleted]
,這時(shí)命令才會(huì)執(zhí)行完畢枚钓,否則永遠(yuǎn)處于執(zhí)行中铅搓。
4.button
的rac_command
與enbaleSignal
不能共存。需要使用帶有enable
參數(shù)的方法初始化command
搀捷。
2.2 設(shè)計(jì)思想
RACCommand
設(shè)計(jì)思想:內(nèi)部signalBlock
為什么要返回一個(gè)信號(hào)星掰,這個(gè)信號(hào)有什么用指煎。
- 在RAC開(kāi)發(fā)中,通常會(huì)把網(wǎng)絡(luò)請(qǐng)求封裝到
RACCommand
至壤,直接執(zhí)行某個(gè)RACCommand
就能發(fā)送請(qǐng)求。- 當(dāng)
RACCommand
內(nèi)部請(qǐng)求到數(shù)據(jù)的時(shí)候像街,需要把請(qǐng)求的數(shù)據(jù)傳遞給外界黎棠,這時(shí)候就需要通過(guò)signalBlock
返回的信號(hào)傳遞了。
如何拿到RACCommand
中返回信號(hào)發(fā)出的數(shù)據(jù):
-
RACCommand
有個(gè)執(zhí)行信號(hào)源executionSignals
木西,這個(gè)是signal of signals
(信號(hào)的信號(hào)),意思是信號(hào)發(fā)出的數(shù)據(jù)是信號(hào),不是普通的類(lèi)型随静。 - 訂閱
executionSignals
就能拿到RACCommand
中返回的信號(hào)八千,然后訂閱signalBlock
返回的信號(hào)燎猛,就能獲取發(fā)出的值。
2.3 應(yīng)用場(chǎng)景:監(jiān)聽(tīng)網(wǎng)絡(luò)請(qǐng)求
// 一般采用懶加載的方式
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
//signalBlock必須要返回一個(gè)信號(hào)沸停,不能傳nil.
//如果不想要傳遞信號(hào)昭卓,直接創(chuàng)建空的信號(hào)[RACSignal empty];
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//信號(hào)的創(chuàng)建是在主線程中{number = 1, name = main}
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]];
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//NSURLSession自動(dòng)切入子線程申請(qǐng)網(wǎng)絡(luò)請(qǐng)求{number = 5, name = (null)}
if (error) {
[subscriber sendError:error];
} else {
NSString *dataString = [[NSString alloc] initWithData:data encoding:kCFStringEncodingUTF8];
[subscriber sendNext:dataString];
//RACCommand中信號(hào)如果數(shù)據(jù)傳遞完候醒,必須調(diào)用sendCompleted火焰,這時(shí)命令才會(huì)執(zhí)行完畢,否則永遠(yuǎn)處于執(zhí)行中。
[subscriber sendCompleted];
}
}] resume];
return nil;
}];
}];
//強(qiáng)引用命令纯赎,不要被銷(xiāo)毀南蹂,否則接收不到數(shù)據(jù)
_command = command;
//command executionSignals是信號(hào)的信號(hào),訂閱它可以拿到signalblock返回的信號(hào)
[[_command executionSignals] subscribeNext:^(id _Nullable x) {
//外層信號(hào)是在主線程中創(chuàng)建的晚顷,所以訂閱信號(hào)也是在主線程中{number = 1, name = main}
NSLog(@"%@" ,x);
[x subscribeNext:^(id _Nullable x) {
//因?yàn)樽顑?nèi)層信號(hào)是在子線程中發(fā)的疗疟,所以也是在子線程中訂閱到這個(gè)信號(hào)的{number = 5, name = (null)}
NSLog(@"%@", x);
}];
}];
//或者 RAC的高級(jí)用法 switchToLatest:
//switchToLatest:用于signal of signals策彤,獲取signal of signals發(fā)出的最新信號(hào),也就是可以直接拿到RACCommand中的信號(hào)
[_command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
//因?yàn)樽顑?nèi)層信號(hào)是在子線程中發(fā)的匣摘,所以也是在子線程中訂閱到這個(gè)信號(hào)的{number = 5, name = (null)}
NSLog(@"%@",x);
}];
//要先訂閱信號(hào)音榜,后面再執(zhí)行命令才能收到返回的數(shù)據(jù) !E跗!
[_command execute:@1];
信號(hào)的監(jiān)聽(tīng):
監(jiān)聽(tīng)當(dāng)前命令是否正在執(zhí)行:
//監(jiān)聽(tīng)命令是否執(zhí)行完畢,默認(rèn)會(huì)來(lái)一次嘴办,可以直接跳過(guò)葛家,skip表示跳過(guò)第一次信號(hào)。
[[_command.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
if (x.boolValue) {
NSLog(@"正在執(zhí)行");
} else {
NSLog(@"執(zhí)行完畢");
}
}];
Output:
正在執(zhí)行
…數(shù)據(jù)…
執(zhí)行完畢
2.4 坑點(diǎn)
2.4.1 rac_command與enable不可同時(shí)存在
若button.rac_command
已賦值底燎,那么就不能再將button.enbale
與Signal
綁定:
// 不能同時(shí)存在
btn.rac_command = self.viewModel.loginCommand;
RAC(btn, enabled) = self.viewModel.enableSignal;
解決辦法:使用帶有Enable
參數(shù)的api
初始化RACComand
双仍。
- (instancetype)initWithEnabled:(nullable RACSignal<NSNumber *> *)enabledSignal signalBlock:(RACSignal<ValueType> * (^)(InputType _Nullable input))signalBlock;
btn.rac_command = [[RACCommand alloc] initWithEnabled:self.enableSignal signalBlock:^RACSignal * _Nonnull(id _Nullable input) {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:nil];
[subscriber sendCompleted];
return nil;
}];
}];
2.4.2 網(wǎng)絡(luò)錯(cuò)誤的獲取
error的獲取并不是使用 command.executionSignals.switchToLatest subscribeError:
來(lái)獲取的朱沃∶┯眨可以用兩種方法來(lái)獲取:
- 訂閱
command.errors
[_command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
// success
}];
[_command.errors subscribeNext:^(NSError * _Nullable x) {
// error
}];
- 訂閱
[command execute:]
@weakify(self);
[[Btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
@strongify(self);
[[self.viewModel.command execute:nil] subscribeNext:^(id _Nullable x) {
// success
} error:^(NSError * _Nullable error) {
// error
}];
}];
2.5 與RACSubject區(qū)別
RACSubject
較為靈活翎卓,建議少用失暴,通常用來(lái)代替delegate
微饥。
區(qū)別用計(jì)算機(jī)網(wǎng)絡(luò)中的術(shù)語(yǔ)來(lái)講:RACSubject
更像“單工”欠橘,而RACCommand
就類(lèi)似于“半雙工”。
RACSubject
只能單向發(fā)送事件蛮拔,發(fā)送者將事件發(fā)送出去讓接收者接收事件后進(jìn)行處理,所以畦韭,RACSubject
可代替代理肛跌,被監(jiān)聽(tīng)者可利用subject
發(fā)送事件衍慎,監(jiān)聽(tīng)者接收事件然后進(jìn)行相應(yīng)的監(jiān)聽(tīng)處理,不過(guò)赠法,事件的傳遞方向是單向的乔夯。
對(duì)于RACCommand
末荐,用HTTP
請(qǐng)求能夠更形象地說(shuō)明其原理,HTTP
請(qǐng)求是由請(qǐng)求者向服務(wù)器發(fā)送一條網(wǎng)絡(luò)請(qǐng)求眶熬,而服務(wù)器接收到請(qǐng)求然后經(jīng)過(guò)相應(yīng)處理后再向請(qǐng)求者返回處理過(guò)后的結(jié)果块请,數(shù)據(jù)流是雙向的墩新,RACCommand
正是如此。當(dāng)我想讓某個(gè)部件進(jìn)行某種會(huì)產(chǎn)生結(jié)果的操作時(shí),利用RACCommand
向此部件發(fā)送執(zhí)行事件狸涌,部件接收到執(zhí)行事件后進(jìn)行相應(yīng)操作處理并也通過(guò)RACCommand
將操作結(jié)果回調(diào)到上層帕胆,使得事件得以雙向流通。
以上的解釋是建立在RACCommand
的事件產(chǎn)生與接收者為同一個(gè)對(duì)象的前提下的芙盘,而RACCommand
也能將事件產(chǎn)生者和訂閱者分離儒老,讓某個(gè)對(duì)象專(zhuān)門(mén)發(fā)送事件,通過(guò)RACCommand
將事件傳遞到對(duì)數(shù)據(jù)進(jìn)行操作處理的對(duì)象薇正,最后囚衔,當(dāng)數(shù)據(jù)處理完后再搭載著RACCommand
把結(jié)果事件傳出來(lái)练湿,并被訂閱者對(duì)象訂閱。
下面的這張圖表明了我對(duì)RACSubject與RACCommand的理解:
3. 常見(jiàn)宏
3.1 RAC()
RAC(TARGET, [KEYPATH, [NIL_VALUE]])
- 這個(gè)宏是最常用的辽俗,
RAC()
總是出現(xiàn)在等號(hào)左邊榆苞,等號(hào)右邊是一個(gè)RACSignal
霞捡,表示的意義是將一個(gè)對(duì)象的一個(gè)屬性和一個(gè)signal
綁定。 -
signal
每產(chǎn)生一個(gè)value(id類(lèi)型)
赊琳,都會(huì)自動(dòng)執(zhí)行[TARGET setValue:value ?: NIL_VALUE forKeyPath:KEYPATH]
躏筏。 - 數(shù)字值會(huì)升級(jí)為
NSNumber *
呈枉,當(dāng)setValue:forKeyPath
時(shí)會(huì)自動(dòng)降級(jí)成基本類(lèi)型(int, float ,BOOL等)
猖辫,所以RAC
綁定一個(gè)基本類(lèi)型的值是沒(méi)有問(wèn)題的。宏定義如下:
#define RAC(TARGET, ...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(RAC_(TARGET, __VA_ARGS__, nil)) \
(RAC_(TARGET, __VA_ARGS__))
應(yīng)用場(chǎng)景:
//Ex:只要文本框文字改變悯姊,就會(huì)修改 label 的文字
RAC(_label, text) = _textField.rac_textSignal;
//Ex:監(jiān)聽(tīng)兩個(gè)文本框的內(nèi)容,有內(nèi)容才允許按鈕點(diǎn)擊
RAC(_login_btn, enabled) = [RACSignal combineLatest:@[_usernama_tf.rac_textSignal, _pwd_tf.rac_textSignal] reduce:^id _Nonnull (NSString *userName, NSString *pwd){
return @(userName.length > 0 && pwd.length > 0);
}];
3.2 RACObeserve()
RACObserve(TARGET, KEYPATH)
- 作用是觀察TARGET的KEYPATH屬性仆嗦,相當(dāng)于KVO岸晦,產(chǎn)生一個(gè)RACSignal启上。最常用的使用,和RAC宏綁定屬性倒慧。宏定義如下:
#define _RACObserve(TARGET, KEYPATH) \
({ \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
})
#if __clang__ && (__clang_major__ >= 8)
#define RACObserve(TARGET, KEYPATH) _RACObserve(TARGET, KEYPATH)
#else
#define RACObserve(TARGET, KEYPATH) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
_RACObserve(TARGET, KEYPATH) \
_Pragma("clang diagnostic pop") \
})
#endif
應(yīng)用場(chǎng)景:雙向綁定
1> Model -> UI RAC(TARGET, ...) = RACObserve(TARGET, KEYPATH);
//如果使用基本數(shù)據(jù)類(lèi)型綁定UI內(nèi)容纫谅,需要使用map函數(shù)溅固,通過(guò)block對(duì)value的數(shù)值進(jìn)行轉(zhuǎn)換后才能夠綁定侍郭。
RAC(name_textField, text) = RACObserve(_person, name);
//rac中傳遞的數(shù)據(jù)都是id類(lèi)型,如果是基本類(lèi)型猛计,需要使用map函數(shù)爆捞,通過(guò)block對(duì)value的數(shù)值進(jìn)行轉(zhuǎn)換后才能夠綁定煮甥。
RAC(age_textField, text) = [RACObserve(_person, age) map:^id _Nullable(id _Nullable value) {
return [value description];
}];
2> UI -> Model
@weakify(self);
[[RACSignal combineLatest:@[[name_tf rac_textSignal], [age_tf rac_textSignal]]] subscribeNext:^(RACTuple * _Nullable x) {
@strongify(self);
self.person.name = x.first;
self.person.age = [x.second integerValue];
}];
3.3 RACTuplePack
把數(shù)據(jù)包裝成RACTuple
。宏定義如下:
#define RACTuplePack(...) \
RACTuplePack_(__VA_ARGS__)
#define RACTuplePack_(...) \
([RACTuplePack_class_name(__VA_ARGS__) tupleWithObjectsFromArray:@[ metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]])
應(yīng)用場(chǎng)景:
RACTuple *tuple = RACTuplePack(@1, @2);
3.4 RACTupleUnpack
把RACTuple
(元組類(lèi))解包成對(duì)應(yīng)的數(shù)據(jù)肌访。按順序給對(duì)象賦值:如果對(duì)象數(shù)量多于元祖內(nèi)對(duì)象數(shù),后面不賦值店煞。宏定義如下:
#define RACTupleUnpack(...) \
RACTupleUnpack_(__VA_ARGS__)
#define RACTupleUnpack_(...) \
metamacro_foreach(RACTupleUnpack_decl,, __VA_ARGS__) \
\
int RACTupleUnpack_state = 0; \
\
RACTupleUnpack_after: \
; \
metamacro_foreach(RACTupleUnpack_assign,, __VA_ARGS__) \
if (RACTupleUnpack_state != 0) RACTupleUnpack_state = 2; \
\
while (RACTupleUnpack_state != 2) \
if (RACTupleUnpack_state == 1) { \
goto RACTupleUnpack_after; \
} else \
for (; RACTupleUnpack_state != 1; RACTupleUnpack_state = 1) \
[RACTupleUnpackingTrampoline trampoline][ @[ metamacro_foreach(RACTupleUnpack_value,, __VA_ARGS__) ] ]
應(yīng)用場(chǎng)景:
RACTuple *tuple = RACTuplePack(@1, @2);
RACTupleUnpack(NSString *title1, NSString *title2, NSString *title3) = tuple;
NSLog(@"%@, %@, %@", title1, title2, title3);
Output:
2018-04-05 14:33:58.859491+0800 RACDemo[12628:374427] 1, 2, (null)
3.5 weakify顷蟀、strongify
@weakify(obj)、@strongify(obj)
一般配套使用羞反,解決引用循環(huán)囤萤。因?yàn)橄到y(tǒng)提供的信號(hào)是始終存在的涛舍,因此在RAC
中所有的block
中,如果出現(xiàn)self/成員變量
掸驱,幾乎百分之百會(huì)循環(huán)引用没佑。
應(yīng)用場(chǎng)景:
@weakify(self); // 定義了一個(gè)__weak的self_weak_變量
[RACObserve(self, name) subscribeNext:^(NSString *name) {
@strongify(self); // 局域定義了一個(gè)__strong的self指針指向self_weak
self.outputLabel.text = name;
}];