本文知識點:RACSignal使用箫津、combine侨糟、@weakify@strongify耀态、Model與UI雙向綁定。
1. 介紹
ReactiveCocoa 接管了蘋果的事件機制轧粟,asyncDisplayKit接管了蘋果的UIKit策治。
- 運用的是Hook(鉤子)思想,Hook是一種用于改變API(應用程序編程接口:方法)執(zhí)行結果的技術兰吟。
- Hook用處:截獲API調用的技術通惫。
- Hook原理:在每次調用一個API返回結果之前,先執(zhí)行你自己的方法混蔼,改變結果的輸出履腋。
1.1 響應式
使用RAC的原因:實現(xiàn)響應式編程。
- 什么是響應式編程:
b = 2; c = 3;
a = b + c; //a = 5;
b = 100; //此時對修改 b 的修改并不會使 a 發(fā)生改變惭嚣。
響應式:當修改b或c的時候遵湖,a同時發(fā)生變化。
- iOS開發(fā)中實現(xiàn)響應式
- 方法一:使用KVO監(jiān)聽對象的屬性值達到這一效果晚吞。但缺點是KVO會統(tǒng)一調用同一個方法延旧,如果監(jiān)聽屬性過多,方法非常難以維護载矿。
- 方法二:ReactiveCocoa是目前實現(xiàn)響應式編程的唯一解決方案垄潮。
1.2 難點
- 學習曲線陡峭
- 在團隊開發(fā)的時候要特別謹慎
- 需要不斷的代碼評審,保證團隊的代碼風格一致
- 開發(fā)的時候調用堆棧深不見底闷盔,提高debug成本
1.3 框架導入
Cocoapods導入ReactiveCocoa5.0以上版本需注意:
- Swift 項目:使用 ReactiveCocoa
弯洗。但是 RAC 依賴于 ReactiveSwift,等于你引入了兩個庫逢勾。
pod 'ReactiveCocoa'
- OC 項目:使用 ReactiveObjC
牡整。這個庫里面包含原來 RAC 2 的全部代碼。
pod 'ReactiveObjC'
- Swift 與 OC 混編:同時引用ReactiveCocoa溺拱、ReactiveObjC 和 ReactiveObjCBridge逃贝。
pod 'ReactiveObjC'
pod 'ReactiveCocoa'
pod 'ReactiveObjCBridge'
2. 使用
2.1 RACSignal
1> 信號的創(chuàng)建
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"123"];
[subscriber sendNext:@"456"];
NSLog(@"%@",subscriber);
// 如果不再發(fā)送數據谣辞,最后發(fā)送信號完成,內部會自動調用[RACDisposable disposable]取消訂閱信號沐扳。
[subscriber sendCompleted];
[subscriber sendError:[NSError errorWithDomain:@"send error" code:0 userInfo:@{}]];
return [RACDisposable disposableWithBlock:^{
// block調用時刻:當信號發(fā)送完成或者發(fā)送錯誤泥从,就會自動執(zhí)行這個block,取消訂閱信號。
// 執(zhí)行完Block后沪摄,當前信號就不再被訂閱了躯嫉。
// 信號銷毀的時候 會執(zhí)行這個閉包
// 用于取消訂閱或者清理資源,當信號發(fā)送完成或者發(fā)送錯誤的時候杨拐,就會自動觸發(fā)它祈餐。
// 使用場景:不想監(jiān)聽某個信號時,可以通過它主動取消訂閱信號哄陶。
NSLog(@"dispose");
}];
}];
//信號被訂閱帆阳。訂閱者不是信號本身 而是這段代碼所處的那個objcect 如在vc中,訂閱者就是vc
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"訂閱next :%@",x);
}];
[signal subscribeError:^(NSError * _Nullable error) {
NSLog(@"訂閱error :%@",x);
}];
[signal subscribeCompleted:^ {
NSLog(@"訂閱completed");
}];
Output:
訂閱next :123 //訂閱的next信號
訂閱next :456
<RACPassthroughSubscriber: 0x60400043aa80>
dispose
//訂閱的error信號(因為代碼順序),但是此時已無效屋吨,因為subscriber發(fā)出了completed信號
<RACPassthroughSubscriber: 0x60400043aa80>
dispose
訂閱completed
<RACPassthroughSubscriber: 0x60400043aa80>
dispose
- RACSignal是冷信號蜒谤,只有被訂閱了才可以工作。
- RACSubscriber:表示訂閱者的意思至扰,用于發(fā)送信號芭逝,
這是一個協(xié)議,不是一個類渊胸,只要遵守這個協(xié)議,并且實現(xiàn)方法才能成為訂閱者
台妆。通過create創(chuàng)建的信號翎猛,都有一個訂閱者,幫助他發(fā)送數據接剩。 - RACDisposable:用于取消訂閱或者清理資源切厘,當信號發(fā)送完成或者發(fā)送錯誤的時候,就會自動觸發(fā)它懊缺。
使用場景:不想監(jiān)聽某個信號時疫稿,可以通過它主動取消訂閱信號。
2> 控件的監(jiān)聽
UI類RAC自動封裝了一些方法鹃两,使用的時候先去框架源文件中找遗座。
以 button 為例:
[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"%@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"%@",error);
} completed:^{
NSLog(@"completed");
}];
3> 信號的合并
類似元組類型,可以一次訂閱多個信號俊扳。
//信號合并
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals;
//reduce中可以通過接受的參數進行計算途蒋,并且返回需要的數值
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(RACGenericReduceBlock)reduceBlock;
Ex1: //元組類型
[[RACSignal combineLatest:@[[name_textField rac_textSignal], [pwd_textField rac_textSignal]]] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"%@ %@", x.first, x.second);
}];
Ex2:
//例如訂閱username、password馋记,reduce里面判斷只有用戶名和密碼同時存在才允許登錄
RACSignal *signal_username = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"abc"];
return nil;
}];
RACSignal *signal_pwd = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"123"];
return nil;
}];
//注意 參數需要自己添加上去
[[RACSignal combineLatest:@[signal_username, signal_pwd] reduce:^id _Nonnull (NSString *username, NSString *password){
return @(username.length > 0 && password.length > 0);
}] subscribeNext:^(id _Nullable x) {
//x是上面的@(bool)
NSLog(@"%@", x);
}];
Output: 1
4> RAC中的循環(huán)引用
因為系統(tǒng)提供的信號是始終存在的号坡,因此在RAC中所有的block中懊烤,如果出現(xiàn)self.
或_成員變量
,幾乎百分之百會循環(huán)引用宽堆。
解決辦法:weak-strong dance(RAC提供了宏 @weakify & @strongify)
2.2 RAC雙向綁定
需要用到兩個重要宏: RAC()腌紧、RACObserve()
,源碼如下:
#define RAC(TARGET, ...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(RAC_(TARGET, __VA_ARGS__, nil)) \
(RAC_(TARGET, __VA_ARGS__))
#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
1> Model -> UI (利用KVO宏)
RAC(TARGET, ...) = RACObserve(TARGET, KEYPATH);
如果使用基本數據類型綁定UI內容畜隶,需要使用map函數壁肋,通過block對value的數值進行轉換后才能夠綁定。
RAC(name_textField, text) = RACObserve(_person, name);
//rac中傳遞的數據都是id類型代箭,如果是基本類型墩划,需要使用map函數,通過block對value的數值進行轉換后才能夠綁定嗡综。
RAC(age_textField, text) = [RACObserve(_person, age) map:^id _Nullable(id _Nullable value) {
return [value description];
}];
2> UI -> Model (訂閱控件發(fā)出的signal)
@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];
}];
// 或
RAC(_person, name) = name_tf.rac_textSignal;
RAC(_person, age) = age_tf.rac_textSignal;
3. MVVM+RAC
MVVM+RAC