ReactiveCocoa (RAC) 是 iOS/Mac 函數式回應式編程框架,核心是訊號代表隨時間變化的值。本文透過四大實戰場景展示 RAC 如何簡化 UI 邏輯。
// 三個 Slider 的值變化訊號
RACSignal *r = [self.redSlider rac_newValueChannelWithNilValue:@0].startWith:@0;
RACSignal *g = [self.greenSlider rac_newValueChannelWithNilValue:@0].startWith:@0;
RACSignal *b = [self.blueSlider rac_newValueChannelWithNilValue:@0].startWith:@0;
// 合併最新值
RACSignal *rgb = [RACSignal combineLatest:@[r, g, b] reduce:^id(NSNumber *r, NSNumber *g, NSNumber *b) {
return @[r, g, b];
}];
// 訂閱更新 View 背景色
[rgb subscribeNext:^(NSArray *arr) {
self.colorView.backgroundColor = [UIColor colorWithRed:[arr[0] floatValue]/255.0
green:[arr[1] floatValue]/255.0
blue:[arr[2] floatValue]/255.0
alpha:1.0];
}];
關鍵:startWith:@0 確保初始值存在,否則需三個滑桿都動一下才觸發。
// 帳號、密碼、同意條款三個訊號
RACSignal *account = self.accountField.rac_textSignal;
RACSignal *password = self.passwordField.rac_textSignal;
RACSignal *agree = self.agreeSwitch.rac_newOnChannel; // UISwitch 專用
// 組合驗證
RAC(self.loginButton, enabled) = [RACSignal combineLatest:@[account, password, agree]
reduce:^id(NSString *acc, NSString *pwd, NSNumber *on) {
return @(acc.length > 2 && pwd.length > 2 && [on boolValue]);
}];
一行程式碼取代傳統多個 addTarget、旗標變數、if-else 判斷。
RAC(self.timeLabel, text) = [[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]]
startWith:[NSDate date]]
map:^id(NSDate *date) {
NSDateComponents *c = [[NSCalendar currentCalendar] components:NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond fromDate:date];
return [NSString stringWithFormat:@"%02ld:%02ld:%02ld", c.hour, c.minute, c.second];
}];
interval: 每秒發送當前日期,map 轉換為格式化字串,自動綁定 Label 文字。
// Cell 內部按鈕點擊
[[[cell.replyButton rac_signalForControlEvents:UIControlEventTouchUpInside]
takeUntil:cell.rac_prepareForReuseSignal] // 關鍵:重用前自動取消訂閱
subscribeNext:^(UIButton *btn) {
[self.navigationController pushViewController:[ReplyViewController new] animated:YES];
}];
// CollectionView Cell 變色
[[[cell.changeButton rac_signalForControlEvents:UIControlEventTouchUpInside]
takeUntil:cell.rac_prepareForReuseSignal]
subscribeNext:^(UIButton *btn) {
cell.backgroundColor = [UIColor grayColor];
}];
takeUntil: 監聽 rac_prepareForReuseSignal,Cell 進入重用池前自動發送 completed 終止訂閱,避免舊訂閱作用於新資料。
| 控件 | 訊號屬性/方法 | 說明 |
|---|---|---|
| UITextField | .rac_textSignal | 文字變化訊號 |
| UISlider | .rac_newValueChannelWithNilValue: | 值變化(含初始 nil 處理) |
| UISwitch | .rac_newOnChannel | 開關狀態變化 |
| UISegmentedControl | .rac_newSelectedSegmentIndexChannelWithNilValue: | 選中索引變化 |
| UIDatePicker | .rac_newDateChannelWithNilValue: | 日期變化 |
| UIButton | .rac_signalForControlEvents: | 指定事件訊號 |
| UITableViewCell | .rac_prepareForReuseSignal | 即將重用訊號 |
| UIViewController | .rac_willDeallocSignal | 即將銷毀訊號 |
// rac_textSignal 簡化實現
- (RACSignal *)rac_textSignal {
@weakify(self);
return [[[[RACSignal defer:^{ @strongify(self); return [RACSignal return:self]; }]
concat:[self rac_signalForControlEvents:UIControlEventAllEditingEvents]]
map:^(UITextField *x) { return x.text; }]
takeUntil:self.rac_willDeallocSignal] // 自動清理
setNameWithFormat:@"%@ -rac_textSignal", self.rac_description;
}
// rac_signalForControlEvents 核心
- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)events {
@weakify(self);
return [[RACSignal createSignal:^(id subscriber) {
@strongify(self);
[self addTarget:subscriber action:@selector(sendNext:) forControlEvents:events];
[self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
[subscriber sendCompleted];
}]];
return [RACDisposable disposableWithBlock:^{
@strongify(self);
[self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:events];
}];
}] setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", self.rac_description, (unsigned long)events];
}
可見 RAC 內部自動管理目標釋放、訂閱生命週期,開發者無需手動移除 Target。
RAC 以訊號組合、轉換、綁定三大核心操作,將命令式的「狀態管理 + 事件回調」轉化為宣告式的「資料流描述」。掌握 combineLatest、map、takeUntil、RAC(target, property) 四大招式,即可輕鬆駕馭複雜 UI 互動邏輯。雖 Swift 時代多用 Combine/RxSwift,但 RAC 思想在維護舊專案、理解響應式本質上仍具參考價值。