ReactiveCocoa基本了解

ReactiveCocoa的基本了解

ReactiveCocoa简称RAC。

RAC的基本结构

信号源
* RACStream
* RACSignal
* RACSubject
* RACSequence
订阅者
* RACSubscriber
* RACMulticastConnection
调度器
* RACScheduler
清洁工
* RACDisposable


RACSignal能产生且只能产生三种事件:next、completed,error。

  • next 表示这个 Signal 产生了一个值

  • completed 表示 Signal 结束,结束信号只标志成功结束,不带值

  • error 表示 Signal 中出现错误,立刻结束

    RACSignal

    RAC核心是Signal,对应的类为RACSignal。它其实是一个信号源,Signal会给它的订阅者(Subscriber)发送一连串的事件,一个Signal可比作流水线中的一段管线,负责决定管线传输什么样的数据。Subscriber是Signal的订阅者,我们将Subscriber比作管线上的工人,它在拿到数据后对其进行加工处理。数据经过加工后要么进入下一条管线继续处理,要么直接当做成品使用。

    Subscriber

    Subscriber我们一般称之为订阅者,它负责处理Signal传出的数据。RACSubscriber初始化的时候会传入nextBlock、 errorBlock、completeBlock,正是这三个block用于处理不同类型的数据信号(或是将处理后的数据抛往下一段管线,或是当做成品送给使用方)。

    Signal获取到数据后,会调用Subscriber的sendNext, sendComplete, sendError方法来传送数据给Subscriber,Subscriber自然也有方法来获取传过来的数据,如:[signal subscribeNext:error:completed]。这样只要没有sendComplete和sendError,新的值就会通过sendNext源源不断地传送过来。

下面用代码介绍RAC的基本使用和注意事项

RAC的信号分为冷信号和热信号
RACSignal的休眠(cold)和激活(hot)状态,也就是所谓的冷信号和热信号。一般情况下,一个RACSignal创建之后都处于cold状态,有人去subscribe才被激活。
RACSubject不一样 创建成功就处于热状态 可发送可接受。对RACSubject对象的每次subscription,都是将这个subscriber加到subscribers数组中,并没有调用 didSubScirbe()

信号的基本创建和订阅

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
   [subscriber sendNext:@"qwqw"];
   //遇到sendError或sendComplete 就结束发送信号
   //[subscriber sendError:nil];
   [subscriber sendCompleted];
   
   return [RACDisposable disposableWithBlock:^{
       NSLog(@"这里是信号结束后需要操作什么,可以return nil");
   }];
}];
//激活冷信号
[signal subscribeNext:^(id x) {
   NSLog(@"%@",x);
}];

RACSignal的副作用
RACSignal在被subscribe的时候可能会产生副作用(Side Effects),就是当一个冷信号被重复订阅的时,导致singnal里的代码重复执行,这可能是你需要的情况,但如果你不要这种情况出现可以用RACMulticastConnection来处理这种情况。

mulitcast 这个方法,首先就创建了一个RACMulticastConnection对象保存参数起来

connect 方法里面会对signal subscribe 也就是执行createBlock执行完毕。
signal就会调用清除方法。

然后用connection的信号订阅。

这时我们在后续操作的subscriNext的signal已经不是原来的signal了,而是didsubscribeBlock为空的signal,所以不管后面有多少次subscribNext都不会让createBlock重复执行。

RACMulticastConnection *connection = [signal multicast:[RACReplaySubject subject]];
[connection connect];
[connection.signal subscribeNext:^(id x) {
   NSLog(@"one -> %@",x);
}];
[connection.signal subscribeNext:^(id x) {
   NSLog(@"two -> %@",x);
}];

结果
2017-03-16 20:51:23.963 MVVMDemo[37845:1040952] 这里是信号结束后需要操作什么,可以return nil 2017-03-16 20:51:23.965 MVVMDemo[37845:1040952] one -> qwqw 2017-03-16 20:51:23.965 MVVMDemo[37845:1040952] two -> qwqw


RACSubject

RACSubject:信号提供者,自己可以充当信号,又能发送信号。
RacSubject创建成功就处于热状态 可发送可接受.对RACSubject对象的每次subscription,都是将这个subscriber加到subscribers数组中,并没有调用 didSubScirbe()

//RACSubject 要提前订阅再发送信号  RACSubject是一对多的
RACSubject *subject = [RACSubject subject];
    
[subject subscribeNext:^(id x) {
   NSLog(@"subject -> %@",x);
}];
    
[subject subscribeNext:^(id x) {
   NSLog(@"subject2 -> %@",x);
}];

[subject sendNext:@(123456)];

RAC使用时的注意事项

使用时需要注意循环引用,@weakify(self) / @strongify(self) 组合解除循环引用;

@weakify(self)
[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]
    subscribeNext:^(id x) {
    @strongify(self)
    [self.viewModel.loginCommand execute:nil];
}];


RAC里把系统很多事件都封装好,我们可以很方便调用并转换为信号模式

1.例如target-action,UIControl的addTaget方法。

[[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *button) {
    NSLog(@"登录按钮被点击");
}];

2.通知

[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"kClickButton" object:nil] subscribeNext:^(NSNotification *notification) {
   //收到通知
   NSLog(@"收到通知");
}];

3.KVO

[RACObserve(self.loginBtn, highlighted) subscribeNext:^(id x) {
   NSLog(@"loginBtn.Highlighted->%@",x);
}];

4.RAC也把基本控件都封装了,方便开发者使用。

[self.userIdTF.rac_textSignal subscribeNext:^(id x) {
   NSLog(@"%@",x);
}];

RAC.png

RAC宏

RAC宏允许直接把信号的输出应用到对象的属性上。RAC宏有两个参数,第一个是需要设置属性值的对象,第二个是属性名。每次信号产生一个next事件,传递过来的值都会应用到该属性上。

RAC( self.userPWTF, backgroundColor) = [validPasswordSignal map:^id(NSNumber *value) {
   return [value boolValue] ? [UIColor clearColor] : [UIColor redColor];
}];

RACCommand类用于表示事件的执行

command的初始化方法中有一个enabledSignal参数,这个signal就是用来指明command能否被执行的。
signalBlock参数在command需要执行时调用,这个block需要返回一个signal用来表示正在执行,之前将allowsConcurrentExecute的值设置为默认值NO,此时command会观察这个signal,而且在这个执行进度完成前,不允许新的执行。如果你需要手动执行command,可以发送消息:-[RACCommand execute:()]

RACCommand *btnCommand = [[RACCommand alloc] initWithEnabled:validUserNameSignal signalBlock:^RACSignal *(id input) {
   return [self loginSignal];
}];

RACSequence类

,可以简单看做是RAC世界的NSArray,RAC增加了-rac_sequence方法,可以使诸如NSArray这些集合类(collection classes)直接转换为RACSequence来使用.

NSArray *arr = @[@1,@2,@3,@4,@5,@6];
[arr.rac_sequence.signal subscribeNext:^(id x) {
   //NSLog(@"x:%@",x);
}];
    
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"jtd",@"name",@"man",@"sex",@"jx",@"jg", nil];
[dict.rac_sequence.signal subscribeNext:^(id x) {
   RACTupleUnpack(NSString *key,NSString *value) = x;
   NSLog(@"key:%@,value:%@",key,value);
   //NSLog(@"%@",x);
}];

RACScheduler 调度器 控制线程

//RACScheduler 调度器 控制线程
//startEagerlyWithScheduler  Eagerly立即 Lazily稍后
//schedulerWithPriority 指定等级的异步并发队列
//信号传递到那个线程deliverOn -> mainThreadScheduler(主线程)  currentScheduler(当前线程)
RAC(self.avatarIV,image) = [[RACSignal startEagerlyWithScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground] block:^(id<RACSubscriber> subscriber) {
   NSError *error;
   NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://ww3.sinaimg.cn/bmiddle/7128be06jw1ei4hfthoj3j20hs0bomyd.jpg"]
                                        options:NSDataReadingMappedAlways
                                          error:&error];
   if(error) {
       [subscriber sendError:error];
   }else{
       [subscriber sendNext:[UIImage imageWithData:data]];
       [subscriber sendCompleted];
   }
}] deliverOn:[RACScheduler mainThreadScheduler]];

RAC信号的处理

RACSignal的每个操作都会返回一个RACsignal。
转换(map)、过滤(filter) 取双层信号中内层信号的值:flattenMap等操作。

1.filter过滤

[[self.userIdTF.rac_textSignal filter:^BOOL(NSString *value) {
   return value.length > 4;//大于4才开启管道 YES开启 NO关闭
}] subscribeNext:^(id x) {
   NSLog(@"%@",x);
}];

2.map转换
新加的map操作通过block改变了事件的数据。map从上一个next事件接收数据,通过执行block把返回值传给下一个next事件。在上面的代码中,map以NSString为输入,取字符串的长度,返回一个NSNumber。

[[[self.userIdTF.rac_textSignal map:^id(NSString *text) {
   return @(text.length);//返回对象类型 改变的对象类型
}] filter:^BOOL(NSNumber *value) {
   return [value integerValue] > 3;
} ] subscribeNext:^(id x) {
   NSLog(@"%@",x);
}];

3.concat:按一定顺序拼接信号
当多个信号发出的时候,有顺序的接收信号。

RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence;
// Contains: A B C D E F G H I 1 2 3 4 5 6 7 8 9
RACSequence *concatenated = [letters concat:numbers];
[concatenated.signal subscribeNext:^(id x) {
   // 注意:第一个信号必须发送完成,第二个信号才会被激活
   //NSLog(@"concatenated - >%@",x);
}];

4.flatten (合并)
switchToLatest 的原理是当有新的signal来的时候,就dispose老的signal,订阅新的signal,而 flatten 不会 dispose 老的 signal

RACSubject *oneSubject = [RACSubject subject];
RACSubject *twoSubject = [RACSubject subject];
RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
   [subscriber sendNext:oneSubject];
   [subscriber sendNext:twoSubject];
   [subscriber sendCompleted];
   return nil;
}];
RACSignal *flattened = [signalOfSignals flatten];
// Outputs: A 1 B C 2
[flattened subscribeNext:^(NSString *x) {
   //NSLog(@"%@", x);
}];
[oneSubject sendNext:@"A"];
[twoSubject sendNext:@"1"];
[oneSubject sendNext:@"B"];
[oneSubject sendNext:@"C"];
[twoSubject sendNext:@"2"];

5.flattenMap 把源信号的内容映射成一个新的信号,信号可以是任意类型
开发中,如果信号发出的值不是信号,映射一般使用Map。如果信号发出的值是信号,映射一般使用FlatternMap。Map底层其实是调用flatternMap,Map中block中的返回的值会作为flatternMap中block中的值。

//把textfield的输入操作转换成另一种输出[NSString stringWithFormat:@"输出:%@",value]的信号
[[self.userPWTF.rac_textSignal flattenMap:^RACStream *(id value) {
   return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {  
       [subscriber sendNext:[NSString stringWithFormat:@"输出:%@",value]];
       return nil;
   }];
}] subscribeNext:^(id x) {
   NSLog(@"flattenMap - >%@",x);
}];

6.merge:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用

//
[[RACSignal merge:@[self.userIdTF.rac_textSignal,self.userPWTF.rac_textSignal]] subscribeNext:^(id x) {
   
   NSLog(@"merge - >%@",x);
}];

7.combineLatest(组合)将多个信号合并起来
combineLatest(组合)将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。

8.switchToLatest(选择最新的信号)

//
RACSubject *switchSignal = [RACSubject subject];
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];

    
// 获取信号中信号最近发出信号,订阅最近发出的信号。
// 注意switchToLatest:只能用于信号中的信号
[signalOfSignals.switchToLatest subscribeNext:^(id x) {
   
   NSLog(@"switchToLatest-> %@",x);
}];
[switchSignal sendNext:signalA];
[switchSignal sendNext:signalB];
[signalA sendNext:@1];
[signalB sendNext:@2];

9.ignore(忽略)
忽略给定的值,注意,这里忽略的既可以是地址相同的对象,也可以是- isEqual:结果相同的值,也就是说自己写的Model对象可以通过重写- isEqual:方法来使- ignore:生效。常用的值的判断没有问题,

[[self.userIdTF.rac_textSignal ignore:@"sunny"] subscribeNext:^(NSString *value) {
       NSLog(@"`sunny` could never appear : %@", value);
}];

10 take(取)
从开始一共取N次的next值,不包括Competion和Error

[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
   [subscriber sendNext:@"1"];
   [subscriber sendNext:@"2"];
   [subscriber sendNext:@"3"];
   [subscriber sendCompleted];
   return nil;
}] take:2] subscribeNext:^(id x) {
   NSLog(@"only 1 and 2 will be print: %@", x);
}];

11 takeUntil(取值,直到某刻结束)
当给定的signal完成前一直取值。

[self.userIdTF.rac_textSignal takeUntil:self.rac_willDeallocSignal];

12.takeUntilBlock(对于每个next值,运行block,当block返回YES时停止取值)

[[self.userIdTF.rac_textSignal takeUntilBlock:^BOOL(NSString *value) {
   return [value isEqualToString:@"stop"];
}] subscribeNext:^(NSString *value) {
   NSLog(@"current value is not `stop`: %@", value);
}];

13then:用于连接两个信号
当第一个信号完成,才会连接then返回的信号

//
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
   
   [subscriber sendNext:@1];
   //第一个信号没有触发sendCompleted 不会触发then
   [subscriber sendCompleted];
   return nil;
}] then:^RACSignal *{
   return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       [subscriber sendNext:@2];
       return nil;
   }];
}] subscribeNext:^(id x) {
   
   // 只能接收到第二个信号的值,也就是then返回信号的值
   NSLog(@"then -> %@",x);
}];

14.distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
过滤,当上一次和当前的值不一样,就会发出内容。在开发中,刷新UI经常使用,只有两次数据不一样才需要刷新

[[self.userIdTF.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
   
   NSLog(@"distinctUntilChanged -> %@",x);
}];

15.timeout 超时,可以让一个信号在一定的时间后,自动报错。

RACSignal *timeOutSignal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
   return nil;
}] timeout:1 onScheduler:[RACScheduler currentScheduler]];
    
[timeOutSignal subscribeNext:^(id x) {
   NSLog(@"timeOutSignal -> %@",x);
} error:^(NSError *error) {
   // 1秒后会自动调用
   NSLog(@"timeOut error-> %@",error);
}];

16.interval 定时:每隔一段时间发出信号

//
[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
   //返回当前时间
   NSLog(@"interval 1ms -> %@",x);
}];

17.delay 延迟发送信号

[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
   [subscriber sendNext:@1];
   return nil;
}] delay:2] subscribeNext:^(id x) {
   
   NSLog(@"delay 2s-> %@",x);
}];

18. retry重试 :只要失败,就会重新执行创建信号中的block,直到成功.

__block int i = 0;
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
   
   if (i == 10) {
       [subscriber sendNext:@1];
   }else{
       [subscriber sendError:nil];
   }
   i++;
   return nil;
}] retry] subscribeNext:^(id x) {
   NSLog(@"retrySignal -> %@",x);
} error:^(NSError *error) {
   NSLog(@"接收到错误");
}];

19.throttle节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。

RACSubject *throttleSignal = [RACSubject subject];

// 节流,在一定时间(1秒)内,不接收任何信号内容,过了这个时间(1秒)获取最后发送的信号内容发出。
[[throttleSignal throttle:1] subscribeNext:^(id x) {
   
   NSLog(@"throttleSignal -> %@",x);
}];

20.reduce聚合信号
reduceblcok中的参数,有多少信号组合,reduceblcok就有多少参数,每个参数就是之前信号发出的内容
reduceblcok的返回值:聚合信号之后的内容。

[[RACSignal combineLatest:@[validPasswordSignal,validUserNameSignal] reduce:^id(NSNumber *usernameValid, NSNumber *passwordValid){
   return @([usernameValid boolValue] && [passwordValid boolValue]);
}] subscribeNext:^(NSNumber *signupActive) {
   self.loginBtn.enabled = [signupActive boolValue];
}];

21.doNext:
你可以看到doNext:是直接跟在按钮点击事件的后面。而且doNext: block并没有返回值。因为它是附加操作,并不改变事件本身。

//
[[[[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
   self.loginBtn.enabled = NO;
}] flattenMap:^RACStream *(id value) {
   return [self loginSignal];
}] subscribeNext:^(id x) {
   self.loginBtn.enabled = YES;
   
}];

上面的信号操作只是RAC中的一小部分,以后会不定时更新。

可以留意我的博客:lemonfan.cn

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

推荐阅读更多精彩内容