ReactiveCocoa 响应式函数编程

简介


ReactiveCocoa(简称为RAC),RAC具有函数响应式编程特性,由Matt Diephouse开源的一个应用于iOS和OS X的新框架。

为什么使用RAC?


因为RAC具有高聚合低耦合的思想所以使用RAC会让代码更简洁,逻辑更清晰。

如何在项目中添加RAC?

纯 swift 项目,继续使用 ReactiveCocoa 。但是 RAC 依赖于 ReactiveSwift ,等于你引入了两个库。

纯 OC 项目,需要使用 ReactiveObjC 。这个库里面包含原来 RAC 2的全部代码。

项目是 swift 和 OC 混编,需要同时引用 ReactiveCocoaReactiveObjCBridge 。但是 ReactiveObjCBridge 依赖于 ReactiveObjC ,所以你就等于引入了 4 个库。

工作原理


 
工作原理

常见类解释


1. Stream - 信号流值 - RACStream类
表示一个基本单元可以为任意值,其值会随着事件的变化而变化,可以在其上进行一些复杂的操作运算(map,filter,skip,take等.)此类不会被经常使用, 多情况下表现为signal和sequences(RACSignal 和RACSequence继承于RACStream类)


1 [[RACObserve(self, reactiveString)
2     filter:^BOOL(NSString *value) {
3         return [value hasPrefix:@"A"];
4 }]
5 subscribeNext:^(NSString *value) {
6         NSLog(@"%@",value);
7 }];

2. Signals - 信号 - RACSignal类

RACSignal能力

什么是Signals?

 
Signals

有订阅者监听时信号才会发信息, Signals会向那个订阅者发送0或多个载有数值的”next”事件,后面跟着一个”complete”事件或一个”error”事件。
Signals会发送三种不同信号给Subscriber

  • next:是可以为nil的新值, RACStream方法只能在这个值上进行操作运算。
  • error:表示在Signals完成之前发生了错误,值不会在RACStream类中存储。
  • completed:表示Signals成功的完成,值不会在RACStream类中存储。
 
订阅者监听
 1 __block int aNumber = 0;
 2 // Signal that will have the side effect of incrementing `aNumber` block
 3 // variable for each subscription before sending it.
 4 RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
 5     aNumber++;
 6     [subscriber sendNext:@(aNumber)];
 7     [subscriber sendCompleted];
 8     return nil;
 9 }];
10         
11 // This will print "subscriber one: 1"
12 [aSignal subscribeNext:^(id x) {
13     NSLog(@"subscriber one: %@", x);
14 }];
15         
16 // This will print "subscriber two: 2"
17 [aSignal subscribeNext:^(id x) {
18     NSLog(@"subscriber two: %@", x);
19 }];

如果需要对信号进行过滤,转换,分解和合并那些值的话则不同的订阅者可能需要使用信号通过不同方式发送的值。

信号处理
1 RACSignal *usernameIsValidSignal = RACObserve(self.viewModel, usernameValid);
2 RAC(self.Button, alpha) = [usernameIsValidSignal
3     map:^(NSNumber *valid) {
4         return valid. boolValue?@1:@0.5;
5 }];

 

3. Subscriber - 订阅者 - RACSubscriber协议
表示能够接收信号的对象,订阅信号才会激活信号,实现RACSubscriber协议的对象都可以为订阅者。
可以通过- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock 方法创建Subscriber。

1 RACSignal *repeatSignal = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] repeat];
2 [repeatSignal subscribeNext: ^(NSDate* time){
3       NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
4       [formatter setDateFormat:@"HH:mm:ss"];
5       NSLog(@"%@",[formatter stringFromDate:time]);
6 }];

4. Subjects - 手动控制信号 - RACSubject  - 热信号
表示可以手动控制信号,
处理流程:创建信号-订阅信号-发送信号

 1 // 1.创建信号
 2 RACSubject *subject = [RACSubject subject];
 3 // 2.订阅信号 First
 4 [subject subscribeNext:^(id x) {
 5     // block调用时刻:当信号发出新值,就会调用.
 6       NSLog(@"FirstSubscribeNext%@",x);
 7 }];
 8 // 2.订阅信号 Second
 9 [subject subscribeNext:^(id x) {
10       // block调用时刻:当信号发出新值,就会调用.
11       NSLog(@"SecondSubscribeNext%@",x);
12 }];
13 // 3.发送信号
14 [subject sendNext:@"1"];
15 [subject sendNext:@"2"];

 

使用 RACSubject 替代代理的方法:

// 需求:
// 1.给当前控制器添加一个按钮,modal到另一个控制器界面
// 2.另一个控制器view中有个按钮,点击按钮,通知当前控制器
    
//步骤一:在第二个控制器.h,添加一个RACSubject代替代理。
@interface TwoViewController : UIViewController

@property (nonatomic, strong) RACSubject *delegateSignal;

@end

//步骤二:监听第二个控制器按钮点击
@implementation TwoViewController
- (IBAction)notice:(id)sender {
    // 通知第一个控制器,告诉它,按钮被点了
    
     // 通知代理
     // 判断代理信号是否有值
    if (self.delegateSignal) {
        // 有值,才需要通知
        [self.delegateSignal sendNext:nil];
    }
}

@end

//步骤三:在第一个控制器中,监听跳转按钮,给第二个控制器的代理信号赋值,并且监听.
@implementation OneViewController 
- (IBAction)btnClick:(id)sender {
    
    // 创建第二个控制器
    TwoViewController *twoVc = [[TwoViewController alloc] init];
    
    // 设置代理信号
    twoVc.delegateSignal = [RACSubject subject];
    
    // 订阅代理信号
    [twoVc.delegateSignal subscribeNext:^(id x) {
       
        NSLog(@"点击了通知按钮");
    }];
    
    // 跳转到第二个控制器
    [self presentViewController:twoVc animated:YES completion:nil];
    
}

@end

也是RAC代码与非RAC代码的Bridge 所以非常有用,此类继承于RACSignal类。

5. ReplaySubject - 手动回放控制信号 - RACReplaySubject - 热信号

表示可以手动控制信号,底层实现和RACSubject不一样,它会先把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock然后调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock。
可以有以下两种处理流程:

处理流程 1:创建信号-订阅信号-发送信号(和Subjects一样)

处理流程 2:创建信号-发送信号-订阅信号

 1 // 1.创建信号
 2 RACReplaySubject *replaySubject = [RACReplaySubject subject];
 3 // 2.发送信号
 4 [replaySubject sendNext:@"1"];
 5 [replaySubject sendNext:@"2"];
 6 // 3.订阅信号 First
 7 [replaySubject subscribeNext:^(id x) {
 8       NSLog(@"FirstSubscribeNext%@",x);
 9 }];
10 // 3.订阅信号 Second
11 [replaySubject subscribeNext:^(id x) {
12       NSLog(@"SecondSubscribeNext%@",x);
13 }];

6. Command- 命令信号 - RACCommand
表示订阅响应Action信号,通常由UI来出发,比如一个Button当控件被触发时会被自动禁用掉。

1 UIButton *reactiveBtn = [[UIButton alloc] init];
2 [reactiveBtn setTitle:@"点我" forState:UIControlStateNormal];
3 reactiveBtn.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(UIButton *input) {
4    NSLog(@"点击了我:%@",input.currentTitle);
5     //返回一个空的信号量
6     return [RACSignal empty];
7 }];

 

7. Sequences- 集合 - RACSequence
表示一个不可变的序列值且不能包含空值,使用-rac_sequence.signal来获取Signal。

1 RACSignal *signal = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal;
2 // Outputs
3 [signal subscribeNext:^(NSString *x) {
4     NSLog(@"%@", x);
5 }];

8. Disposables- 清理订阅 - RACDisposable
表示用于取消信号的订阅,当一个signal被subscriber后,当执行sendComplete或sendError时subscriber会被移除,或者手动调用[disposable dispose]进行移除操作。
当subscriber被移除后,所有该subscriber相关的工作都会被停止或取消,如http请求,资源也会被释放。

9. Scheduler- 计划 - RACScheduler
表示一个信号队列,是信号执行任务时所在的队列或者信号执行完成后将结果放到队列里执行,它支持取消对列里的执行并总是串行执行。

RAC常用宏


RACObserve(TARGET, KEYPATH)
表现形式:RACObserve(self, stringProperty)
KVO的简化版本 相当于对TARGET中KEYPATH的值设置监听,返回一个RACSignal

RAC(TARGET, ...)
表现形式:RAC(self, stringProperty) = TextField.rac_textSignal
第一个是需要设置属性值的对象,第二个是属性名
RAC宏允许直接把信号的输出应用到对象的属性上
每次信号产生一个next事件,传递过来的值都会应用到该属性上

RACChannelTo(TARGET, ...)
RACChannelTo 用于双向绑定
RACChannelTo(self, stringProperty)=RACChannelTo(self.label, text) ;

应用说明:

 1     //RACObserve 应用
 2     [self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
 3         self.textString = x;
 4     }];
 5 
 6     RACSignal *textFieldSignal = RACObserve(self, self.textString);
 7     [textFieldSignal subscribeNext:^(id  _Nullable x) {
 8         NSLog(@"textField: %@", x);
 9     }];
10 
11 
12     //RAC 应用
13     RACSignal *signal = RAC(self, self.textString) = self.textField.rac_textSignal;
14     [signal subscribeNext:^(id  _Nullable x) {
15         NSLog(@"text: %@", x);
16     }];

RAC结构图


 
RAC结构图

RAC基础使用


创建一个TextField名为usernameTextField 设置监听TextField

1 [self.usernameTextField.rac_textSignal 
2     subscribeNext:^(id x){
3     NSLog(@"%@", x);
4 }];

filter:如果想添加一个条件 只输出x的长度大于3的,可以使用filter操作来实现这个目的

1 //filter应用
2 [self.usernameTextField.rac_textSignal
3     filter:^BOOL(NSString* text){
4     return text.length > 3;
5 }];
filter
map:把text转换成length进行输出,使用map可以对信号进行转换,一个源信号转换成另外一个新的信号输出
 1 [[[self.usernameTextField.rac_textSignal
 2 map:^id(NSString*text){
 3   return @(text.length);
 4 }]
 5 filter:^BOOL(NSNumber*length){
 6   return[length integerValue] > 3;
 7 }]
 8 subscribeNext:^(id x){
 9   NSLog(@"%@", x);
10 }];
map

信号可聚合也可以分割

聚合: 多个信号可以聚合成一个新的信号,这个可以是任何类型的信号

1 RACSignal *signal =
2 [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]
3                     reduce:^id(NSNumber*usernameValid, NSNumber *passwordValid){
4                       return @([usernameValid boolValue]&&[passwordValid boolValue]);
5                     }];

分割:一个信号可以有很多subscriber,也就是作为很多后续步骤的源

1 RACSignal *signal = self.usernameTextField.rac_textSignal;
2     [signal subscribeNext:^(id x) {
3         NSLog(@"1111");
4     }];
5     [signal subscribeNext:^(id x) {
6         NSLog(@"2222");
7     }];
8 }

RAC设置Button的ControlEvents

1 [[self.signInButton
2      rac_signalForControlEvents:UIControlEventTouchUpInside]
3      subscribeNext:^(id x) {
4      NSLog(@"button click");
5 }];
rac_signalForControlEvents

登陆功能举例说明
需要实现登陆功能需要点击登陆button
 1 - (RACSignal *)signInSignal {
 2     return [RACSignal createSignal:^RACDisposable *(id subscriber){
 3      [self.signInService 
 4      signInWithUsername:self.usernameTextField.text
 5                password:self.passwordTextField.text
 6                complete:^(BOOL success){
 7                     [subscriber sendNext:@(success)];
 8                     [subscriber sendCompleted];
 9        }];
10      return nil;
11     }];
12 }
13 [[[[self.signInButton
14      rac_signalForControlEvents:UIControlEventTouchUpInside]
15      doNext:^(id x){
16        self.signInButton.enabled =NO;
17        self.signInFailureText.hidden =YES;
18      }]
19 
20     flattenMap:^id(id x){        
21         return[self signInSignal];
22     }]
23 
24     subscribeNext:^(NSNumber*signedIn){
25     self.signInButton.enabled =YES;
26     BOOL success =[signedIn boolValue];
27     self.signInFailureText.hidden = success;
28     if(success){
29         [self performSegueWithIdentifier:@"signInSuccess" sender:self];
30     }
31 }];

 flattenMap:[self signInSignal]返回的也是signal,所以是信号中的信号,使用这个操作把按钮点击事件转换为登录信号,同时还从内部信号发送事件到外部信号。

 doNext:为一个附加操作,在一个next事件发生时执行的逻辑,而该逻辑并不改变事件本身。

 

流程

RAC高级使用


error 和 completed,节流,线程,延伸,其他

内存管理

ReactiveCocoa设计的一个目标就是支持匿名生成管道这种编程风格。到目前为止,在你所写的所有响应式代码中,这应该是很直观的。
为了支持这种模型,ReactiveCocoa自己持有全局的所有信号。如果一个signal有一个或多个订阅者,那这个signal就是活跃的。如果所有的订阅者都被移除了,那这个信号就能被销毁了。

如何取消订阅一个signal?
在一个completed或者error事件之后,订阅会自动移除。你还可以通过RACDisposable 手动移除订阅。

RACSignal的订阅方法都会返回一个RACDisposable实例,它能让你通过dispose方法手动移除订阅。这个方法并不常用到,但是还是有必要知道可以这样做。

 1 RACSignal *backgroundColorSignal =
 2   [self.searchText.rac_textSignal 
 3       map:^id(NSString *text) { 
 4           return [self isValidSearchText:text] ? 
 5               [UIColor whiteColor] : [UIColor yellowColor]; 
 6   }]; 
 7 
 8 RACDisposable *subscription = 
 9 [backgroundColorSignal 
10     subscribeNext:^(UIColor *color) {
11         self.searchText.backgroundColor = color; 
12 }]; 
13 
14 [subscription dispose];​

避免循环引用
在ReactiveCocoa中提供了避免循环引用的方法
@weakify宏让你创建一个弱引用的影子对象(如果你需要多个弱引用,你可以传入多个变量),
@strongify让你创建一个对之前传入@weakify对象的强引用。

 1 @weakify(self) 
 2 [[self.searchText.rac_textSignal 
 3 map:^id(NSString *text) { 
 4     return [self isValidSearchText:text] ? 
 5         [UIColor whiteColor] : [UIColor yellowColor]; 
 6 }] 
 7 subscribeNext:^(UIColor *color) { 
 8     @strongify(self) 
 9     self.searchText.backgroundColor = color; 
10 }];​

signal能发送3种不同类型的事件
Next
Completed
Error

当应用获取访问社交媒体账号的权限时,用户会看见一个弹框。这是一个异步操作,因此把这封装进一个signal是很好的选择

 1 -(RACSignal *)requestAccessToTwitterSignal {
 2 // 1 - define an error 
 3 NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain 
 4                                            code:RWTwitterInstantErrorAccessDenied 
 5                                        userInfo:nil];
 6                                    
 7 // 2 - create the signal 
 8 @weakify(self) 
 9 return [RACSignal createSignal:^RACDisposable *(id subscriber) { 
10     // 3 - request access to twitter 
11     @strongify(self) 
12     [self.accountStore requestAccessToAccountsWithType:self.twitterAccountType 
13            options:nil 
14         completion:^(BOOL granted, NSError *error) {
15         // 4 - handle the response 
16         if (!granted) { 
17            [subscriber sendError:accessError]; 
18         } else { 
19             [subscriber sendNext:nil]; 
20             [subscriber sendCompleted]; 
21         } 
22     }]; 
23 return nil; 
24 }]; 
25 }​

then:then方法会等待completed事件的发送,然后再订阅由then block返回的signal。这样就高效地把控制权从一个signal传递给下一个。

 1 [[[[self requestAccessToTwitterSignal] 
 2 then:^RACSignal *{ 
 3     @strongify(self) 
 4     return self.searchText.rac_textSignal; 
 5 }] 
 6 filter:^BOOL(NSString *text) { 
 7     @strongify(self) 
 8     return [self isValidSearchText:text]; 
 9 }] 
10 subscribeNext:^(id x) { 
11     NSLog(@"%@", x); 
12 } error:^(NSError *error) { 
13     NSLog(@"An error occurred: %@", error); 
14 }];​
 
then

实时搜索内容方法

  • 创建请求链接方法
1 -(SLRequest *)requestforTwitterSearchWithText:(NSString *)text { 
2 NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/1.1/search/tweets.json"]; 
3 NSDictionary *params = @{@"q" : text}; 
4 SLRequest *request = [SLRequest   requestForServiceType:SLServiceTypeTwitter 
5                                     requestMethod:SLRequestMethodGET 
6                                               URL:url 
7                                        parameters:params]; 
8 return request; 
9 }​
  • 创建请求signal
 1 -(RACSignal *)signalForSearchWithText:(NSString *)text { 
 2 // 1 - define the errors 
 3 NSError *noAccountsError = [NSError errorWithDomain:RWTwitterInstantDomain 
 4                                                code:RWTwitterInstantErrorNoTwitterAccounts 
 5                                            userInfo:nil]; 
 6 NSError *invalidResponseError = [NSError errorWithDomain:RWTwitterInstantDomain 
 7                                                     code:RWTwitterInstantErrorInvalidResponse 
 8                                                     userInfo:nil]; 
 9                                                     
10 // 2 - create the signal block 
11 @weakify(self) 
12 return [RACSignal createSignal:^RACDisposable *(id subscriber) { 
13     @strongify(self); 
14     
15     // 3 - create the request 
16     SLRequest *request = [self requestforTwitterSearchWithText:text]; 
17     
18     // 4 - supply a twitter account 
19     NSArray *twitterAccounts = [self.accountStore accountsWithAccountType:self.twitterAccountType];       
20     if (twitterAccounts.count == 0) { 
21         [subscriber sendError:noAccountsError]; 
22     } else { 
23         [request setAccount:[twitterAccounts lastObject]]; 
24         
25     // 5 - perform the request 
26     [request performRequestWithHandler: ^(NSData *responseData, 
27             NSHTTPURLResponse *urlResponse, NSError *error) { 
28         if (urlResponse.statusCode == 200) { 
29         
30             // 6 - on success, parse the response 
31             NSDictionary *timelineData = [NSJSONSerialization JSONObjectWithData:responseData 
32                                             options:NSJSONReadingAllowFragments 
33                                               error:nil]; 
34             [subscriber sendNext:timelineData]; 
35             [subscriber sendCompleted]; 
36         } else { 
37             // 7 - send an error on failure 
38             [subscriber sendError:invalidResponseError]; 
39         } 
40     }]; 
41 } 
42 return nil; 
43 }];
44 }
  • 使用flattenMap来把每个next事件映射到一个新的signal
 1 [[[[[self requestAccessToTwitterSignal] 
 2 then:^RACSignal *{ 
 3     @strongify(self) 
 4     return self.searchText.rac_textSignal; 
 5 }] 
 6 filter:^BOOL(NSString *text) { 
 7     @strongify(self) 
 8     return [self isValidSearchText:text]; 
 9 }] 
10 flattenMap:^RACStream *(NSString *text) { 
11     @strongify(self) 
12     return [self signalForSearchWithText:text]; 
13 }] 
14 subscribeNext:^(id x) { 
15     NSLog(@"%@", x); 
16 } error:^(NSError *error) { 
17     NSLog(@"An error occurred: %@", error); 
18 }];

线程

在subscribeNext:error:中的数据没有在主线程(Thread 1)中执行,更新UI只能在主线程中执行,所以更新UI需要转到主线程中执行。

要怎么更新UI呢?
通常的做法是使用操作队列但是ReactiveCocoa有更简单的解决办法,在flattenMap:之后添加一个deliverOn:操作就可以转到主线程上了。
:如果你看一下RACScheduler类,就能发现还有很多选项,比如不同的线程优先级,或者在管道中添加延迟。

 1 [[[[[[self requestAccessToTwitterSignal] 
 2 then:^RACSignal *{ 
 3     @strongify(self) 
 4     return self.searchText.rac_textSignal; 
 5 }] 
 6 filter:^BOOL(NSString *text) { 
 7     @strongify(self) 
 8     return [self isValidSearchText:text]; 
 9 }] 
10 flattenMap:^RACStream *(NSString *text) { 
11     @strongify(self) 
12     return [self signalForSearchWithText:text]; 
13 }] 
14 deliverOn:[RACScheduler mainThreadScheduler]] 
15 subscribeNext:^(id x) { 
16     NSLog(@"%@", x); 
17 } error:^(NSError *error) { 
18     NSLog(@"An error occurred: %@", error); 
19 }];

异步加载图片

 1 -(RACSignal *)signalForLoadingImage:(NSString *)imageUrl { 
 2 RACScheduler *scheduler = [RACScheduler 
 3     schedulerWithPriority:RACSchedulerPriorityBackground]; 
 4     
 5 return [[RACSignal createSignal:^RACDisposable *(id subscriber) { 
 6     NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrl]]; 
 7     UIImage *image = [UIImage imageWithData:data]; 
 8     [subscriber sendNext:image]; 
 9     [subscriber sendCompleted]; 
10     return nil; 
11 }] subscribeOn:scheduler]; 
12 }

首先获取一个后台scheduler,来让signal不在主线程执行。然后,创建一个signal来下载图片数据,当有订阅者时创建一个UIImage。最后是subscribeOn:来确保signal在指定的scheduler上执行。

 1  -(UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
 2 UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"TableViewCell"];
 3 [[[[self signalForLoadingImage:tweet.profileImageUrl] 
 4 takeUntil:cell.rac_prepareForReuseSignal] 
 5 deliverOn:[RACScheduler mainThreadScheduler]] 
 6 subscribeNext:^(UIImage *image) { 
 7     cell.twitterAvatarView.image = image; 
 8 }];
 9 return cell;
10 }

 

cell是重用的,可能有脏数据,所以上面的代码首先重置图片。然后创建signal来获取图片数据。你之前也遇到过deliverOn:这一步,它会把next事件发送到主线程,这样subscribeNext:block就能安全执行了。

cell.rac_prepareForReuseSignal:Cell复用时的清理。
takeUntil:当给定的signal完成前一直取值

节流

每次输入一个字,搜索都会马上执行。如果你输入很快(或者只是一直按着删除键),这可能会造成应用在一秒内执行好几次搜索,这很不理想。
更好的解决方法是,当搜索文本在短时间内,比如说500毫秒,不再变化时,再执行搜索。
在filter之后添加一个throttle步骤:

 1 [[[[[[[self requestAccessToTwitterSignal] 
 2 then:^RACSignal *{ 
 3     @strongify(self) 
 4     return self.searchText.rac_textSignal; 
 5 }] 
 6 filter:^BOOL(NSString *text) { 
 7     @strongify(self) 
 8     return [self isValidSearchText:text]; 
 9 }] 
10 throttle:0.5] 
11 flattenMap:^RACStream *(NSString *text) { 
12     @strongify(self) 
13     return [self signalForSearchWithText:text]; 
14 }] 
15 deliverOn:[RACScheduler mainThreadScheduler]] 
16 subscribeNext:^(NSDictionary *jsonSearchResult) { 
17     NSArray *statuses = jsonSearchResult[@"statuses"]; 
18     NSArray *tweets = [statuses linq_select:^id(id tweet) { 
19         return [RWTweet tweetWithStatus:tweet]; 
20     }]; 
21     [self.resultsViewController displayTweets:tweets]; 
22 } error:^(NSError *error) { 
23     NSLog(@"An error occurred: %@", error); 
24 }];

throttle:只有当前一个next事件在指定的时间段内没有被接收到后,throttle操作才会发送next事件。

代替代理

如果想在其他地方监听到tableView的代理信息则需要设置如下方法

[[tableView rac_signalForSelector:@selector(tableView:didSelectRowAtIndexPath:) fromProtocol:@protocol(UITableViewDelegate) ] subscribeNext:^(RACTuple * x) {
    NSLog(@"点击了");
}];

rac_signalForSelector: fromProtocol: 要先绑定在设置代理

疑点问题总结

A、什么是冷信号与热信号

冷热信号的概念源于.NET框架Reactive Extensions(RX)中的Hot Observable和Cold Observable,两者的区别是:

1、
Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;
Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。

2、
Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;
Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。

举例说明冷信号:
__block int aNumber = 0;

//1. 创建信号,等待信号发送,只有订阅了才会调用block函数内的发送(sendNext)
RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
//3. 订阅完成后,会执行block进行发送此消息
aNumber++;
[subscriber sendNext:@(aNumber)];
[subscriber sendCompleted];
return nil;
}];

// 2. 添加订阅,等到订阅消息
[aSignal subscribeNext:^(id x) {
NSLog(@"subscriber one: %@", x);
}];

说明:处理顺序:1. 创建信号,等待信号发送 -> 2. 添加订阅,等到订阅消息 -> 3. 订阅完成后,会执行block进行发送此消息


B、RAC中的RACSiganl类中几乎都是冷信号,但是也有特例,RAC中提供了两个热信号的类:RACSubject和RACReplaySubject

RACSubject和RACReplaySubject区别:

RACSubject
(1)这个类会保存所有的订阅者,一旦被订阅,就会保存订阅者,等待有值发出的时候,就会告诉所有的订阅者;
(2)处理流程:创建信号-订阅信号-发送信号


RACReplaySubject
(1)是继承RACSubject的所以和RACSubject一样会保存订阅者,然后比父类不同的是它还会保存所有发送过的值,
供有新订阅者订阅的时候发送它漏过的值
(2)处理流程 a:创建信号-订阅信号-发送信号(和Subjects一样)
处理流程 b:创建信号-发送信号-订阅信号

 

posted on 2018-02-05 10:32  东方&#128058;  阅读(374)  评论(0编辑  收藏  举报

导航