一. 存在的問題
UI更新必須依賴程序員在指定位置手動(dòng)觸發(fā)。UI更新的邏輯會(huì)遍布各地難以維護(hù),而且調(diào)用者必須明確的知道更新的邏輯,因?yàn)閡pdatA,updateB,先調(diào)后調(diào)有時(shí)也會(huì)發(fā)生截然不同的效果。
舉個(gè)??,假如有一個(gè)類似網(wǎng)易云音樂的列表頁。歌曲列表請求成功后,賦值tabelView的數(shù)據(jù)源。注意,此時(shí)我們還有執(zhí)行[tableView reloadData],此外,可能還需要調(diào)更新歌曲數(shù),更新專輯封面等等。只要和這個(gè)數(shù)據(jù)源有關(guān)的UI元素,我們都需要手動(dòng)觸發(fā)一遍。更重要的是要更新哪些UI,需要我們自己判斷,極容易引發(fā)bug。上述是網(wǎng)絡(luò)請求的,在類似的多交互頁面中,常常還包括側(cè)滑刪除、批量增加、名稱修改等等對數(shù)據(jù)源的操作,而這些都意味著我們需要手動(dòng)觸發(fā)UI更新。當(dāng)然,有些共同的更新,我們可以抽成一個(gè)方法,但還是難以從根本上避免伴隨著用戶事件及數(shù)據(jù)改變的,散落各地的UI更新邏輯。
二. RAC的解決方案
還是上面那個(gè)場景,使用RAC后,只需要關(guān)心數(shù)據(jù)源如果變化,無需關(guān)心數(shù)據(jù)源變化后所引發(fā)的UI層面的連鎖反應(yīng)。加載更多,往數(shù)據(jù)源中加對應(yīng)數(shù)據(jù);刪除某首歌,刪除數(shù)據(jù)源中的對應(yīng)數(shù)據(jù)。修改數(shù)據(jù)后,訂閱(subscribe)了數(shù)據(jù)源的相關(guān)元素會(huì)自動(dòng)接收到數(shù)據(jù)變化的信號,并作出對應(yīng)改變。
光是上述效果我們用KVO也能做到,RAC更獨(dú)特的地方在于用信號量(Signal)統(tǒng)一了所有變化,包括網(wǎng)絡(luò)回調(diào)、KVO、通知、block等等,抽象成信號量后,我們可以統(tǒng)一進(jìn)行邏輯操作,包括combine、filter、map等等,這些操作才是強(qiáng)力的工具。整個(gè)流程為事件流->信號流->邏輯操作->訂閱。
三. 適合使用RAC的場景舉例
- 登錄注冊:登錄注冊常見的需求有必須同時(shí)滿足xxx條件,按鈕才能啟用。
@weakify(self);
//自動(dòng)響應(yīng)登錄按鈕是否可用
RAC(self.loginButton, enabled) = [RACSignal
combineLatest:@[self.accountTextField.rac_textSignal,
self.pwdTextField.rac_textSignal]
reduce:^(NSString *userName,NSString *pwd){
return @(userName.length > 0 && pwd.length > 0);
}];
//account屬性自動(dòng)響應(yīng)更新值
[[self.accountTextField.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
return value.length > 0;
}] subscribeNext:^(NSString * _Nullable x) {
@strongify(self);
self.loginViewModel.account = x;
}];
//password屬性自動(dòng)響應(yīng)更新值
[[self.pwdTextField.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
return value.length > 0;
}] subscribeNext:^(NSString * _Nullable x) {
@strongify(self);
self.loginViewModel.password = x;
}];
//按鈕點(diǎn)擊事件轉(zhuǎn)成信號流,調(diào)用VM中的方法執(zhí)行。
[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
@strongify(self);
[[self.loginViewModel.loginCommand executing] subscribeNext:^(id _Nullable x) {
NSLog(@"登錄成功");
}];
[[self.loginViewModel.loginCommand execute:nil] subscribeError:^(NSError * _Nullable error) {
NSLog(@"登錄失敗");
}];
}];
vm.m
- (void)setupCommand{
@weakify(self);
_loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
@strongify(self);
[self loginWithUserName:self.account password:self.password done:^(NSDictionary *result) {
NSString *userId = [result objectForKey:@"user_id"];
if (userId) {
[subscriber sendCompleted];
}else{
[subscriber sendError:nil];
}
}];
return nil;
}];
}];
}