一、簡介
ReactiveCocoa 可以說是結合了函數(shù)式編程和響應式編程的框架,也可稱其為函數(shù)響應式編程(FRP)框架,強調(diào)一點,RAC 最大的優(yōu)點是提供了一個單一的、統(tǒng)一的方法去處理異步的行為,包括 delegate 方法, blocks 回調(diào),target-action 機制,notifications 和 KVO。
導入
在項目的 podfile 文件中添加
# RAC
pod 'ReactiveObjC'
在使用時導入
#import <ReactiveObjC/ReactiveObjC.h>
二、基本使用
1.button 添加點擊事件
[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
//button點擊
}];
2.代替 KVO 監(jiān)聽
#import "ViewController.h"
#import <ReactiveObjC/ReactiveObjC.h>
@interface ViewController ()
@property(nonatomic,assign)NSInteger num;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(50, 100, 100, 100)];
button.backgroundColor = [UIColor orangeColor];
@weakify(self);
//subscribeNext 收到信號
[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
@strongify(self);
//button點擊
self.num++;
}];
[self.view addSubview:button];
//RACObserve(TARGET, KEYPATH):監(jiān)聽某個對象的某個屬性,返回的是一個信號
[RACObserve(self,num) subscribeNext:^(id _Nullable x) {
//
NSLog(@"%@",x);
}];
//忽略值為 1 的情況,不執(zhí)行回調(diào)
[[RACObserve(self, num) ignore:@1]subscribeNext:^(id _Nullable x) {
NSLog(@"忽略%@",x);
}];
}
@end
3.監(jiān)聽輸入變化
self.textField = [[UITextField alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:self.textField];
[[self.textField rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"%@",x);
}];
4.通知回調(diào)
//RAC監(jiān)聽回調(diào)
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"DJTESTNOTI" object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSLog(@"x===%@",x);
}];
//發(fā)送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"DJTESTNOTI" object:@"444"];
5.手勢回調(diào)
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
[self.view addGestureRecognizer:tap];
[[tap rac_gestureSignal] subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
NSLog(@"點擊");
}];
6.數(shù)組和字典遍歷
//數(shù)組遍歷
NSArray *array = @[@"111",@"222",@"333",@"444"];
[array.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"數(shù)組%@",x);
}];
//字典遍歷
NSDictionary *dict = @{@"111":@"-111",@"222":@"-222",@"333":@"-333",@"444":@"-444"};
[dict.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"字典%@",x);
}];
二、宏
1. RAC
RAC(TARGET, [KEYPATH, [NIL_VALUE]])用于給某個對象的某個屬性綁定
// 給某個對象的某個屬性綁定一個信號,只要產(chǎn)生信號,就會把信號的內(nèi)容給對象的屬性進行賦值
// 給label的text屬性綁定一個輸入值的信號
RAC(self.titleLabel,text) = RACObserve(self, inputContentText);
2.RACObserve
RACObserve(TARGET, KEYPATH)監(jiān)聽某個對象的某個屬性,返回的是一個信號
3.RACTuplePack和RACTupleUnpack
RACTuplePack把數(shù)據(jù)包裝成 RACTuple(元組類),被包裝的數(shù)據(jù)必須是 object 類數(shù)據(jù)
// RACTuplePack:把一些數(shù)據(jù)包裝成元組類,可用于信號間的數(shù)據(jù)傳輸
// 注意:被包裝的數(shù)據(jù)必須是 object類數(shù)據(jù)
RACTuple *tuple = RACTuplePack(@"數(shù)據(jù)1",@1,@[@"1",@"2",@"3",@"4"]);
RACTupleUnpack把 RACTuple(元組類)解包成對應的數(shù)據(jù),解包參數(shù)的順序及數(shù)據(jù)類型要和包裝數(shù)據(jù)時的順序及類型保持一致
// 參數(shù):需要解析數(shù)據(jù)生成出來對應的變量名
// 注意:解包參數(shù)的順序及數(shù)據(jù)類型要和包裝數(shù)據(jù)時的順序及類型保持一致
RACTupleUnpack(NSString *str,NSNumber *num, NSArray *arr) = tuple;
三、信號組合
1. concat
concat按一定順序拼接信號,當多個信號發(fā)出的時候,有順序的接收信號,依賴關系把一組信號串聯(lián)起來,前面一個信號 complete,后面一個信號才開始發(fā)揮作用。
//比如A請求依賴B請求,只有B請求完成之后才能執(zhí)行A請求或操作
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalA"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalB"];
//因為A依賴B,所以只需要在B里面聲明發(fā)送完成
[subscriber sendCompleted];
return nil;
}];
//注意點:如果一個操作后面還被其他操作依賴,比如signalB,需要在其內(nèi)部發(fā)送完數(shù)據(jù)后聲明發(fā)送完成,[subscriber sendCompleted];
RACSignal *contatSignal = [signalB concat:signalA];
[contatSignal subscribeNext:^(id x) {
NSLog(@"(concat)結果:%@",x);
//輸出結果
}];
2.then
then用于連接兩個信號,當?shù)谝粋€信號完成,才會連接 then 返回的信號。
//A請求依賴B請求,只有B請求完成之后才能執(zhí)行A的請求或操作,需要注意:這個方法最后只能拿到A的值,如果B不需要傳值,只需要先進行某些操作的時候可以用then
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalA"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalB"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *thenSignal = [signalB then:^RACSignal *{
return signalA;
}];
[thenSignal subscribeNext:^(id x) {
NSLog(@"(then)結果:%@",x);
//輸出結果
}];
then 與 concat 區(qū)別:then 監(jiān)聽不到第一個信號的值,共同點都是必須第一個信號完成,第二個信號才會激活
3. merge
merge 把多個信號合并為一個信號,任何一個信號有新值的時候就會調(diào)用。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalA"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalB"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *mergeSignal = [signalA merge:signalB];
[mergeSignal subscribeNext:^(id x) {
NSLog(@"(merge)結果:%@",x);
}];
4. zipWith
zipWith 把兩個信號壓縮成一個信號,只有當兩個信號同時發(fā)出信號內(nèi)容時,并且把兩個信號的內(nèi)容合并成一個元組,才會觸發(fā)壓縮流的 next 事件。
//信號壓縮,這個方法其實和rac_liftSelector本質(zhì)時一樣的,把多個信號壓縮成一個信號,只有被壓縮的信號全部發(fā)出消息時才能調(diào)用
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalA"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalB"];
return nil;
}];
//zipSignal是有順序的
RACSignal *zipSignal = [signalB zipWith:signalA];
[zipSignal subscribeNext:^(id x) {
//x是接受到的所有數(shù)據(jù)包裝成的元組
NSLog(@"(zipWith)結果:%@",x);
//輸出結果
/*--TIME:14:53:55.966000+0800
(zipWith)結果:<RACTwoTuple: 0x600003a2df90> (
signalB,
signalA
)*/
}];
5. reduce
reduce信號聚合,參數(shù)需要自己添加
//多用于登錄邏輯
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalA"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signalB"];
return nil;
}];
// 聚合
// reduce:信號聚合,參數(shù)需要自己添加
// 常見的用法,(先組合再聚合)。combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock
// reduce中的block簡介:
// reduceblcok中的參數(shù),有多少信號組合,reduceblcok就有多少參數(shù),每個參數(shù)就是之前信號發(fā)出的內(nèi)容
// reduceblcok的返回值:聚合信號之后的內(nèi)容。
RACSignal *combineSignal = [RACSignal combineLatest:@[signalA, signalB] reduce:^id(NSString *signalA, NSString *signalB){
NSLog(@"%@----%@", signalA, signalB);
//block:只要任意一個信號發(fā)出內(nèi)容,就會調(diào)用
//block參數(shù)個數(shù):由信號決定
//block參數(shù)類型:block的參數(shù)就是信號發(fā)出值
//把兩個信號中的值聚合成哪個值
return @(signalA.length && signalB.length);
}];
[combineSignal subscribeNext:^(id x) {
NSLog(@"(combineLatest)結果:%@",x);
//輸出結果
//--TIME:14:53:55.966000+0800 (combineLatest)結果:1
}];
[[signalA combineLatestWith:signalB] subscribeNext:^(id _Nullable x) {
//x是接收兩個信號合并后的數(shù)據(jù)包裝成的元組(RACTuple)
NSLog(@"(combineLatestWith)結果:%@",x);
//輸出結果
/*--TIME:14:53:55.966000+0800
(combineLatestWith)結果:<RACTwoTuple: 0x600003578620> (
signalA,
signalB
)
*/
}];
6.其他
combineLatest將多個信號合并起來,并且拿到各個信號的最新的值,必須每個合并的 signal 至少都有過一次 sendNext,才會觸發(fā)合并的信號。
combineLatestWith 合并兩個信號,當兩個信號都有 sendNext 才會觸發(fā)合并的信號。
四、MVVM+RAC
示例如下:
DJViewController
#import <UIKit/UIKit.h>
@interface DJViewController : UIViewController
@end
#import "DJViewController.h"
#import "DJViewModel.h"
#import "DJTableViewCell.h"
@interface DJViewController ()<UITableViewDataSource, UITableViewDelegate>
@property (strong, nonatomic) UITableView *tableView;
@property (strong, nonatomic) DJViewModel *reqVM;
@end
@implementation DJViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self setUI];
[self ViewModelEvent];
}
#pragma mark - 界面設置
- (void)setUI {
self.tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 100)];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.view addSubview:self.tableView];
}
#pragma mark - ViewModel事件
- (void)ViewModelEvent {
[self.reqVM.reqCommand execute:nil];
@weakify(self);
[self.reqVM.refreshUISubject subscribeNext:^(id x) {
@strongify(self);
[self.tableView reloadData];
}];
}
#pragma mark - UITableView配置
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.reqVM.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
DJTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"OrderCell"];
if (!cell) {
cell = [[DJTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"OrderCell"];
}
cell.model = self.reqVM.dataArray[indexPath.row];
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 80;
}
#pragma mark - 懶加載
- (DJViewModel *)reqVM {
if (!_reqVM) {
_reqVM = [[DJViewModel alloc] init];
}
return _reqVM;
}
@end
-
[self.reqVM.reqCommand execute:nil];方法為執(zhí)行reqCommand事件命令,reqCommand是DJViewModel中網(wǎng)絡請求事件。 -
[self.reqVM.refreshUISubject subscribeNext:^(id x) { @strongify(self); [self.tableView reloadData]; }];
此方法為訂閱DJViewModel中網(wǎng)絡請求完成時發(fā)送的信號(refreshUISubject),也就是說當網(wǎng)絡請求完成之后會執(zhí)行block中的刷新tableView方法。
DJViewModel
#import <Foundation/Foundation.h>
#import <ReactiveObjC/ReactiveObjC.h>
@interface DJViewModel : NSObject
@property (nonatomic, strong) RACSubject *refreshUISubject;
@property (strong, nonatomic) RACCommand *reqCommand;
@property (nonatomic, strong) NSArray *dataArray;
@end
#import "DJViewModel.h"
#import "DJModel.h"
@interface DJViewModel ()
@end
@implementation DJViewModel
- (instancetype)init {
if (self = [super init]) {
[self or_initialize];
}
return self;
}
- (void)or_initialize {
//網(wǎng)絡請求信號
[self.reqCommand.executionSignals.switchToLatest subscribeNext:^(NSDictionary *dic) {
NSArray *items = dic[@"items"];
NSMutableArray *arr = [NSMutableArray array];
for (NSDictionary * dict in items) {
DJModel *model = [[DJModel alloc]init];
model.name = dict[@"name"];
[arr addObject:model];
}
self.dataArray = [arr copy];
[self.refreshUISubject sendNext:nil];
}];
[[self.reqCommand.executing skip:1] subscribeNext:^(id x) {
if ([x isEqualToNumber:@(YES)]) {
// [MBProgressHUD showCircleHud:nil];
}else {
// [MBProgressHUD closeHud:nil];
}
}];
}
#pragma mark - 懶加載
- (RACCommand *)reqCommand {
if (!_reqCommand) {
_reqCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
//因為要把請求的數(shù)據(jù)傳出去,所以要把網(wǎng)絡請求包裝在信號里
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//網(wǎng)絡請求
NSDictionary *dic = @{@"items":@[@{@"name":@"hhhh"}]};
[subscriber sendNext:dic];
[subscriber sendCompleted];
return nil;
}];
//返回網(wǎng)絡請求信號
return signal;
}];
}
return _reqCommand;
}
- (RACSubject *)refreshUISubject {
if (!_refreshUISubject) {
_refreshUISubject = [RACSubject subject];
}
return _refreshUISubject;
}
- (NSArray *)dataArray {
if (!_dataArray) {
_dataArray = [[NSArray alloc] init];
}
return _dataArray;
}
@end
-
refreshUISubject屬性是通知控制器刷新 UI 的信號,其功能類似于代理。reqCommand屬性是網(wǎng)絡請求事件,暴露在 .h 文件的原因是讓控制器來決定什么時候發(fā)起事件,也就是說什么時候發(fā)起網(wǎng)絡請求。 -
or_initialize中第一個方法是訂閱reqCommand(網(wǎng)絡請求)事件中的信號發(fā)出的值,也就是網(wǎng)絡請求成功后發(fā)送的數(shù)據(jù)。第二個方法的功能是監(jiān)聽reqCommand事件過程,其block中的值返回 YES 是,代表事件正在執(zhí)行,所以在這里面可以加一個正在加載的菊花,當返回值為 NO 時,代表事件執(zhí)行完成,把正在加載菊花去掉。 - 懶加載
- (RACCommand *)reqCommand方法中就是網(wǎng)絡請求事件,block里面的signal信號作用是把網(wǎng)絡請求的數(shù)據(jù)發(fā)送給or_initialize中第一個方法的訂閱者。訂閱者拿到數(shù)據(jù)后執(zhí)行字典轉(zhuǎn)模型操作,然后發(fā)送暴露在 .h 文件中的refreshUISubject信號給訂閱此信號的控制器,通知他刷新tableView。
DJViewModel、DJTableViewCell
#import <Foundation/Foundation.h>
@interface DJModel : NSObject
@property(nonatomic,assign)NSInteger num;
@end
#import "DJModel.h"
@implementation DJModel
@end
#import <UIKit/UIKit.h>
#import "DJModel.h"
@interface DJTableViewCell : UITableViewCell
@property (nonatomic, strong) DJModel *model;
@end
#import "DJTableViewCell.h"
@interface DJTableViewCell ()
@end
@implementation DJTableViewCell
- (void)setModel:(DJModel *)model {
self.textLabel.text = model.name;
}
@end