ReactiveCocoa简单介绍

一 导入ReactiveCocoa框架

通常都会使用CocoaPods(用于管理第三方框架的插件)帮助我们导入
podfile如果只描述pod 'ReactiveCocoa', '~> 4.0.2-alpha-1',会导入不成功
因为用到了swift的一些东西
需要在podfile加上use_frameworks,重新pod install 才能导入成功

ReactiveCocoa常见的一些类:
RACSignal:信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。
1.信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出
2.默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。
3.如何订阅信号:调用信号RACSignal的subscribeNext就能订阅
简单使用:
// 1.创建信号 RACSubscriber是订阅者
RACSignal *racsignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 3.发送信号
[subscriber sendNext:@100];
return nil;
}];
// 2.订阅信号
[racsignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

RACSubscriber:表示订阅者的意思用于发送一个信号,这是一个协议,不是一个类,只要遵守这个协议并且实现方法才能成为订阅者,通过create创建信号,都有一个订阅者,帮助他发送数据
RACDisposable:用于取消订阅者或者清理资源,当信号发送完成或者发送错误的时候就会自动触发取消订阅
使用场景:不想监听某个信号时候可以用他主动取消订阅信号
例如:

// 1.创建信号
RACSignal *racsignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 3.发送信号
[subscriber sendNext:@100];

  // 订阅者subscriber不会被销毁永远不会取消订阅(在不主动取消的情况下)
  _subscriber = subscriber;// _subscriber成员变量强引用一次
  self.disposable = [RACDisposable disposableWithBlock:^{
      NSLog(@"信号被取消订阅");
  }];
  return self.disposable;

}];
// 2.订阅信号 subscribeNext在函数内部创建订阅者
[racsignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

// 取消订阅
[self.disposable dispose];

RACSubject:信号提供者,自己可以充当信号又可以发送信号
使用场景,通常用来代替代理,有了它就可以不用定义代理了。
RACReplaySubject:重复提供信号类 RACSubject的子类
RACReplaySubject和RACSubject的区别:
RACReplaySubject可以先发送信号在订阅信号,RACSubject不可以。

RACSubject使用步骤:
// 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block
RACSubject *subject = [RACSubject subject];

// 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 返回的是订阅者

RACDisposable *posable1 = [subject subscribeNext:^(id x) {
NSLog(@"第一个订阅者%@",x);
}];
RACDisposable *posable2 = [subject subscribeNext:^(id x) {
NSLog(@"第二个订阅者%@",x);
}];

// RACSubject:底层实现和RACSignal不一样。
// 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
// 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。

//3.发送信号 sendNext:(id)value
[subject sendNext:@100];
[posable1 dispose];// 取消订阅信号
[subject sendNext:@100];

RACReplaySubject:使用步骤
RACReplaySubject *subjest = [RACReplaySubject subject];

//2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
RACDisposable *posable1 = [subjest subscribeNext:^(id x) {
    NSLog(@"第一个订阅者%@",x);
}];

RACDisposable *posable2 = [subjest subscribeNext:^(id x) {
    NSLog(@"第二个订阅者%@",x);
}];
//调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock
[subjest sendNext:@100];
[posable1 dispose];// 取消订阅

[subjest sendNext:@120];

RAC中的 集合类
RACTuple:元组类,类似NSArray,用来包装值

RACSequence:Rac中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典
RACSequence使用场景:字典转模型

RACTuple简单使用:
// 将数组转换层一个元组 RACTuple
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@"1",@"2",@3,@4,@5]];
// 将元组转换为rac中的集合类RACSequence
RACSequence *sequence = tuple.rac_sequence;
// 在将集合类(RACSequence)转换为信号(RACSignal)
RACSignal *signal = sequence.signal;
// 订阅信号,激活信号,会自动把集合中的所有值,遍历出来
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

//可以链式调用
[tuple.rac_sequence.signal subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];

遍历字典:
NSDictionary *dict = @{@"name":@"程倩",@"age":@"26",@"sex":@"男"};
// 遍历字典
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {

    NSString *key = x[0];
    NSString *value = x[1];
    NSLog(@"%@  %@",key,value);
    
}];

第二种遍历方式:
NSDictionary *dict = @{@"name":@"程倩",@"age":@"26",@"sex":@"男"};
// 遍历字典
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {

    // RACTupleUnpack  相当于 NSString *key = x[0];
    //                       NSString *value = x[1];
    RACTupleUnpack(NSString *key,NSString *value) = x;
    NSLog(@"%@  %@",key,value);
    
}];

字典转模型:
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
NSMutableArray *flags = [NSMutableArray array];
_flags = flags;

// rac_sequence注意点:调用subscribeNext,并不会马上执行nextBlock,而是会等一会。
[dictArr.rac_sequence.signal subscribeNext:^(id x) {
    // 运用RAC遍历字典,x:字典
    FlagItem *item = [FlagItem flagWithDict:x];
    [flags addObject:item];
}];

// 3.3 RAC高级写法:
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];

NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
// map:映射的意思,目的:把原始值value映射成一个新值
// array: 把集合转换成数组
// 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
NSArray *flags = [[dictArr.rac_sequence map:^id(id value) {
    return [FlagItem flagWithDict:value];
}] array];

ReactiveCocoa开发中常见用法:

1.代替代理
rac_signalForSelector:用于替代代理
//rac_signalForSelector:将某个select转换为一个信号,然后订阅这个信号,系统调用这个函数的时候会发出信号
// 可以用来代替代理(但是不能传值)如果想传值使用RACSubject
[[self rac_signalForSelector:@selector(didReceiveMemoryWarning)] subscribeNext:^(id x) {
NSLog(@"系统调用了didReceiveMemoryWarning函数");
}];

  1. 代替KVO:
    rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变
    // 代替KVO监听属性的改变这个函数需要导入NSObject+RACKVOWrapper.h头文件,这个文件默认没有导入
    [self rac_observeKeyPath:@"name" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {

    NSLog(@"rac_observeKeyPath监听到值改变了");

}];
//也可以将值改变转换为一个信号 然后订阅这个信号
[[self rac_valuesForKeyPath:@"name" observer:nil] subscribeNext:^(id x) {
    NSLog(@"rac_valuesForKeyPath监听到值改变了");
}];

3.监听事件:
rac_signalForControlEvents:用于监听某个事件
//监听按钮点击事件,返回一个信号,然后直接订阅这个信号
[[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"按钮被点击了");
}];
4.代替通知
rac_addObserverForName:用于监听某个通知
// 代替通知中心
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"通知名称" object:nil] subscribeNext:^(id x) {
NSLog(@"收到通知");
}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"通知名称" object:nil];

  1. 监听文本框文字改变
    rac_textSignal:只要文本框发出改变就会发出这个信号
    // 监听文本框值的改变
    [[self.textfield rac_textSignal] subscribeNext:^(id x) {
    NSLog(@"%@",x);//x是文本框中的值
    }];

  2. 处理当界面有多次请求时,需要都获取到数据时,才能展示界面(两个信号都发送的 时候才会调用最后的select)
    // 6.处理多个请求,都返回结果的时候,统一做处理.
    RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    // 发送请求1
    [subscriber sendNext:@"发送请求1"];
    return nil;
    }];

    RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    // 发送请求2
    [subscriber sendNext:@"发送请求2"];
    return nil;
    }];
    // 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
    [self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];

ReactiveCocoa常见宏

1.RAC(TARGET, [KEYPATH, [NIL_VALUE]]):用于给某个对象的某个属性绑定。
// 只要文本框中的文字改变就会修改lable中的文字
RAC(self.label,text) = self.textfield.rac_textSignal;
2.RACObserve(self, name):监听某个对象的某个属性,返回的是信号。
//RACObserve 监听某个对象的某个值,返回的是一个信号,其实就是ac_valuesForKeyPath函数
[RACObserve(self, name) subscribeNext:^(id x) {

    NSLog(@"%@",x);
}];
  1. @weakify(Obj)和@strongify(Obj),一般两个都是配套使用,解决循环引用问题.
    @weakify(self);// 将self转换成弱指针引用
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {

    @strongify(self) // 将self转换为强指针引用,只在当前block中有效
    
    NSLog(@"%@",self);
    
    return nil;
    

    }];
    _signal = signal;

4.RACTuplePack:把数据包装成RACTuple(元组类)
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@10,@20);

5.RACTupleUnpack:把RACTuple(元组类)解包成对应的数据。
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@"xmg",@20);
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
// name = @"xmg" age = @20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;

RACMulticastConnection:当一个信号被多次订阅的时候,为了保证创建信号时避免多次调用创建信号中的block造成副作用可以使用RACMulticastConnection
RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建

简单使用:
// RACMulticastConnection使用步骤:
// 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
// 2.创建连接 RACMulticastConnection *connect = [signal publish];
// 3.订阅信号,注意:订阅的不在是之前的信号,而是连接的信号。 [connect.signal subscribeNext:nextBlock]
// 4.连接 [connect connect]

// RACMulticastConnection底层原理:
// 1.创建connect,connect.sourceSignal -> RACSignal(原始信号)  connect.signal -> RACSubject
// 2.订阅connect.signal,会调用RACSubject的subscribeNext,创建订阅者,而且把订阅者保存起来,不会执行block。
// 3.[connect connect]内部会订阅RACSignal(原始信号),并且订阅者是RACSubject
// 3.1.订阅原始信号,就会调用原始信号中的didSubscribe
// 3.2 didSubscribe,拿到订阅者调用sendNext,其实是调用RACSubject的sendNext
// 4.RACSubject的sendNext,会遍历RACSubject所有订阅者发送信号。
// 4.1 因为刚刚第二步,都是在订阅RACSubject,因此会拿到第二步所有的订阅者,调用他们的nextBlock


// 需求:假设在一个信号中发送请求,每次订阅一次都会发送请求,这样就会导致多次请求。
// 解决:使用RACMulticastConnection就能解决.

// RACMulticastConnection:解决重复请求问题
// 1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    NSLog(@"发送请求");
    [subscriber sendNext:@1];
    return nil;
}];

// 2.创建连接
RACMulticastConnection *connect = [signal publish];

// 3.订阅信号,
// 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
[connect.signal subscribeNext:^(id x) {
    NSLog(@"订阅者一信号");
}];
[connect.signal subscribeNext:^(id x) {
    NSLog(@"订阅者二信号");
}];
// 4.连接,激活信号
[connect connect];

RACCommand:Rac中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递包装到这个类中,可以很方便的监听事件的执行过程。

使用场景:监听按钮点击和网络请求。
// RACCommand:处理事件
// RACCommand:不能返回一个空的信号,会闪退
// 创建命令
RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
// 当前block执行命令的时候调用
// input 执行命令传入参数
NSLog(@"%@",input);
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"执行命令产生数据"];
return nil;
}];
}];
// 如果要拿到执行命令产生的数据,执行命令返回一个信号,直接订阅信号
RACSignal *signal = [command execute:@1];
// 订阅信号
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];

executionSignals:信号中的信号,发送的数据就是信号

//executionSignals:信号中的信号,发送的数据就是信号
// 订阅信号,必须要在执行命令之前订阅
[command.executionSignals subscribeNext:^(RACSignal *x) {
[x subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}];
[command execute:@100];

switchToLatest:获取最新发送的信号,只能用于信号中信号

// switchToLatest获取最新发送的信号,只能用于信号中信号
// 获取到最新发送的信号直接订阅
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[command execute:@100];

监听任务是否执行完毕:

RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"执行命令传入参数%@",input);

  return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
     
      [subscriber sendNext:@"执行命令产生数据"];
      // 发送完成
      [subscriber sendCompleted];
      return nil;
  }];

}];
// 监听任务是否执行完成
[command.executing subscribeNext:^(id x) {
if([x boolValue]==YES)
{
NSLog(@"当前任务正在执行");
}else{
NSLog(@"当前任务执行没有在执行");
}
}];

[command execute:@0];

不正确之处,欢迎补充
测试代码 https://github.com/CharType/ReactiveCocoaTest

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容