ReactiveCocoa(RAC)框架

04/07/2017 13:49 下午 posted in  ReactiveCocoa

以下是RAC的Github主页:ReactiveCocoa
以及官方给出的用法链接

本文转载于:http://yimouleng.com/2015/12/20/ios-ReactiveCocoa/

##第一部分 简单使用

文本框事件
原来我们在使用textFiled的时候我们需要写到

[textField addTarget:self action:@selector(textChanged:) forControlEvents:UIControlEventEditingChanged];

然后实现textChanged:方法,在RAC中,对于文本框的监听,是非常简单的一件事情,看如下代码:

UITextField * textField = ({
       UITextField * textField = [[UITextField alloc]init];
       textField.backgroundColor = [UIColor cyanColor];
       
       textField;
   });
  [self.view addSubview:textField];
   
   @weakify(self); //  __weak __typeof__(self) self_weak_ = self;
   
   [textField mas_makeConstraints:^(MASConstraintMaker *make) {
       
       @strongify(self);    // __strong __typeof__(self) self = self_weak_;
       make.size.mas_equalTo(CGSizeMake(180, 40));
       make.center.equalTo(self.view);
   }];
   
   [[textField rac_signalForControlEvents:UIControlEventEditingChanged]
    subscribeNext:^(id x) {
        LxDBAnyVar(x);
    }];
   [textField.rac_textSignal subscribeNext:^(NSString *x) {
       LxDBAnyVar(x);
   }];

打印结果:

📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 12
📍__31-[ViewController textFiledTest]_block_invoke241 + 211🎈 x = <UITextField: 0x7fe810c51a90; frame = (97.5 313.5; 180 40); text = '123'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x7fe810f58fb0>; layer = <CALayer: 0x7fe810c51600>>
📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 123
📍__31-[ViewController textFiledTest]_block_invoke241 + 211🎈 x = <UITextField: 0x7fe810c51a90; frame = (97.5 313.5; 180 40); text = '1231'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x7fe810f58fb0>; layer = <CALayer: 0x7fe810c51600>>
📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 1231
📍__31-[ViewController textFiledTest]_block_invoke241 + 211🎈 x = <UITextField: 0x7fe810c51a90; frame = (97.5 313.5; 180 40); text = '12312'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x7fe810f58fb0>; layer = <CALayer: 0x7fe810c51600>>
📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 12312
📍__31-[ViewController textFiledTest]_block_invoke241 + 211🎈 x = <UITextField: 0x7fe810c51a90; frame = (97.5 313.5; 180 40); text = '123123'; clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x7fe810f58fb0>; layer = <CALayer: 0x7fe810c51600>>
📍__31-[ViewController textFiledTest]_block_invoke_2 + 215🎈 x = 123123

我们很容易的监听到textFiled中发生的变化,其中x的类型默认为id类型, 我们已知它的类型的时候我们可以将其改变,就像上面代码,将id改成了NSString类型。

手势

self.view.userInteractionEnabled = YES;
    UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc]init];
    [[tap rac_gestureSignal] subscribeNext:^(UITapGestureRecognizer * tap) {
        LxDBAnyVar(tap);
    }];
    [self.view addGestureRecognizer:tap];

为了方便,我们直接添加到self.view上,点击屏幕,得到打印结果:

📍__29-[ViewController gestureTest]_block_invoke + 184🎈 tap = <UITapGestureRecognizer: 0x7fa2e3e1f9f0; state = Ended; view = <UIView 0x7fa2e3e20b70>; target= <(action=sendNext:, target=<RACPassthroughSubscriber 0x7fa2e3c064f0>)>>

通知

[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil] subscribeNext:^(NSNotification * notification) {
       
       LxDBAnyVar(notification);
   }];

我们建立了一个通知,叫做进入后台, 当程序进入后台的时候通知相应,当我们用RAC写通知的时候,我们有一个好处,就是不用removeObserver通知,因为RAC通知的监听者师RAC自己,它会帮你管理释放方法。可以看方法实现如下:

- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object {
	@unsafeify(object);
	return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
		@strongify(object);
		id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) {
			[subscriber sendNext:note];
		}];

		return [RACDisposable disposableWithBlock:^{
			[self removeObserver:observer];
		}];
	}] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object];

###定时器

//1. 延迟某个时间后再做某件事
[[RACScheduler mainThreadScheduler]afterDelay:2 schedule:^{
    LxPrintAnything(rac);
}];

//2. 每间隔多长时间做一件事
[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]]subscribeNext:^(NSDate * date) {
    
    LxDBAnyVar(date);
}];

这是定时器最常用的两种写法,第一种方法,延迟时间去做某件事,更改afterDelay的属性。
第二种方法,每间隔多长时间做一件事,更改interval属性。

###代理

UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"RAC" message:@"ReactiveCocoa" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Ensure", nil];
   
   [[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple * tuple) {
       
       LxDBAnyVar(tuple);
       
       LxDBAnyVar(tuple.first);
       LxDBAnyVar(tuple.second);
       LxDBAnyVar(tuple.third);
   }];
   [alertView show];
   //	更简单的方式:
   [[alertView rac_buttonClickedSignal]subscribeNext:^(id x) {
       
       LxDBAnyVar(x);
   }];

用RAC去写代理的时候,会有局限,只能取代没有返回值的代理方法,什么是没有返回值的代理呢?比如说tableView的代理方法:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

这两个方法一个返回的是CGFloat,一个是void,RAC只能取代void的代理。

###KVO

UIScrollView * scrollView = [[UIScrollView alloc]init];
scrollView.delegate = (id<UIScrollViewDelegate>)self;
[self.view addSubview:scrollView];

UIView * scrollViewContentView = [[UIView alloc]init];
scrollViewContentView.backgroundColor = [UIColor yellowColor];
[scrollView addSubview:scrollViewContentView];

@weakify(self);

[scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
    
    @strongify(self);
    make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(80, 80, 80, 80));
}];

[scrollViewContentView mas_makeConstraints:^(MASConstraintMaker *make) {
    
    @strongify(self);
    make.edges.equalTo(scrollView);
    make.size.mas_equalTo(CGSizeMake(CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)));
}];

[RACObserve(scrollView, contentOffset) subscribeNext:^(id x) {
   
    LxDBAnyVar(x);
}];

用RAC写KVO的好处就是方法简单,keypath有代码提示。

##第二部分 进阶

###信号

- (RACSignal *)loginSignal
  {
      return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
          
          RACDisposable * schedulerDisposable = [[RACScheduler mainThreadScheduler]afterDelay:2 schedule:^{
             
              if (arc4random()%10 > 1) {
                  
                  [subscriber sendNext:@"Login response"];
                  [subscriber sendCompleted];
              }
              else {
                  
                  [subscriber sendError:[NSError errorWithDomain:@"LOGIN_ERROR_DOMAIN" code:444 userInfo:@{}]];
              }
          }];
          
          return [RACDisposable disposableWithBlock:^{
              
              [schedulerDisposable dispose];
          }];
      }];
  }

RAC的核心就是RACSignal,也就是信号,我们可以直接创建信号createSignal,并发送它sendNext,当信号完成后我们同时用dispose方法销毁它。发送信号,我们同时也要订阅信号,订阅信号代码如下:

[signal subscribeNext:^(id x) {
     
     LxDBAnyVar(x);
 } error:^(NSError *error) {
     
     LxDBAnyVar(error);
 } completed:^{
     
     LxPrintAnything(completed);
 }];

在信号发送的时候, 错误的时候,以及完成的时候,我们都可以得到相应。

##信号的处理

###map (映射)

UITextField * textField = ({
        UITextField * textField = [[UITextField alloc]init];
        textField.backgroundColor = [UIColor cyanColor];
        
        textField;
    });
    [self.view addSubview:textField];
    
    @weakify(self); //  __weak __typeof__(self) self_weak_ = self;
    
    [textField mas_makeConstraints:^(MASConstraintMaker *make) {
        
        @strongify(self);    // __strong __typeof__(self) self = self_weak_;
        make.size.mas_equalTo(CGSizeMake(180, 40));
        make.center.equalTo(self.view);
    }];

    [[textField.rac_textSignal map:^id(NSString *text) {
        
       LxDBAnyVar(text);
        
        return @(text.length);
        
    }] subscribeNext:^(id x) {
         LxDBAnyVar(x);
    }];

map这个函数,在这里不是地图的意思,代表映射。map能做的事情就是把监听的rac_textSignal所返回的值,替换成别的就像上面代码中的text的长度。

###filter

为了方便演示,我就不再赋值创建textField的代码了,请到Demo中查看

[[[textField.rac_textSignal map:^id(NSString *text) {
        
       LxDBAnyVar(text);
        
        return @(text.length);
        
    }]filter:^BOOL(NSNumber *value) {
        
        return value.integerValue > 3;
        
    }] subscribeNext:^(id x) {
         LxDBAnyVar(x);
    }];

filter是个BOOL值,它代表的是一个条件,当这个条件发生的时候才会作出相应,比如上面代码中,当长度大于3的时候,才会打印x的值。

###delay

//创建信号
    RACSignal * signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"rac"];
        [subscriber sendCompleted];
        return nil;
    }]delay:2];
    LxPrintAnything(start);
    //创建订阅者
    [signal subscribeNext:^(id x) {
        LxDBAnyVar(x);
    }];

delay的作用就是延迟,或者说等待,如上,等待2秒之后打印了x。

###startWith

RACSignal * signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
//        [subscriber sendNext:@"123"];//startWith:@"123"等同于这句话 也就是第一个发送,主要是位置
        [subscriber sendNext:@"rac"];
        [subscriber sendCompleted];
        return nil;
    }]startWith:@"123"];
    LxPrintAnything(start);
    //创建订阅者
    [signal subscribeNext:^(id x) {
        LxDBAnyVar(x);
    }];

startWith也就是最开始的意思,看以上代码 startWith:@"123"等同于[subscriber sendNext:@"123"] 也就是第一个发送,主要是位置.

###timeOut

[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        [[RACScheduler mainThreadScheduler]afterDelay:3 schedule:^{
            
            [subscriber sendNext:@"rac"];
            [subscriber sendCompleted];
        }];
        
        return nil;
    }] timeout:2 onScheduler:[RACScheduler mainThreadScheduler]]
     subscribeNext:^(id x) {
         
         LxDBAnyVar(x);
     } error:^(NSError *error) {
         
         LxDBAnyVar(error);
     } completed:^{
         
         LxPrintAnything(completed);
     }];
     ```

上面代码的意思就是,我设置了超时限制为(timeout)2秒钟,但是我代码延迟3秒钟发送,超时了,所以这条信息发生错误,会走error的方法。 这种情况可以用在封装http client中,当然你可能遇到别的需求,也需要它。

###take – skip

RACSignal * signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
[subscriber sendNext:@"rac1"];
[subscriber sendNext:@"rac2"];
[subscriber sendNext:@"rac3"];
[subscriber sendNext:@"rac4"];
[subscriber sendCompleted];
return nil;
}]take:2];//Skip

[signal subscribeNext:^(id x) {
LxDBAnyVar(x);
}];


比如说我们发送了很多次请求
>	take表示我们只取前两次
	skip表示跳过前两次
	takeLast表示倒数的前两次
	takeUntil这个值比较特殊,他后面的参数是个信号,它的意思是,当takeUntil发送这个信号的时候,上面的发送信号就会停止发送。


接下来是几个block回调方法
>	takeWhileBlock BOOL值,意思是当返回YES的时候,订阅者才能收到信号
	skipWhileBlock BOOL值,意思是当返回YES的时候,订阅者就会跳过信号,NO的时候才接受
	skipUntilBlock BOOL值,意思是 返回NO的时候,不会收到消息, 直到返回YES的时候才开始收消息。

###即时搜索优化 (throttle,distinctUntilChanged,ignore)

UITextField * textField = [[UITextField alloc]init];
textField.backgroundColor = [UIColor cyanColor];
[self.view addSubview:textField];

@weakify(self);

[textField mas_makeConstraints:^(MASConstraintMaker *make) {

   @strongify(self);
   make.size.mas_equalTo(CGSizeMake(180, 40));
   make.center.equalTo(self.view);

}];
//throttle 后面是个时间 表示rac_textSignal发送消息,0.3秒内没有再次发送就会相应,若是0.3内又发送消息了,便会在新的信息处重新计时
//distinctUntilChanged 表示两个消息相同的时候,只会发送一个请求
//ignore 表示如果消息和ignore后面的消息相同,则会忽略掉这条消息,不让其发送
[[[[[[textField.rac_textSignal throttle:0.3] distinctUntilChanged] ignore:@""] map:^id(id value) {

   return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
       
       //  network request
       [subscriber sendNext:value];
       [subscriber sendCompleted];
       
       return [RACDisposable disposableWithBlock:^{
           
           //  cancel request
       }];
   }];

}]switchToLatest] subscribeNext:^(id x) {

   LxDBAnyVar(x);

}];

以上代码,是用textField模拟一个即时搜索优化的功能,其中参数如下:

>	throttle 后面是个时间 表示rac_textSignal发送消息,0.3秒内没有再次发送就会相应,若是0.3内又发送消息了,便会在新的信息处重新计时
	distinctUntilChanged 表示两个消息相同的时候,只会发送一个请求
	ignore 表示如果消息和ignore后面的消息相同,则会忽略掉这条消息,不让其发送


这样做,是不是给服务器减小了很多的压力,更是节省了我们大量的代码。 其中我们用map建立了一个新的信号,我们知道textField的改变是一个信号, map就是在这个信号上,又加了一个信号,即signal of signals。
订阅者所打印的消息x则是,map发出的信号。我们可以再map中发送新的信号,以及取消信号disposable.
当我们用map发送信号的时候,我们则需要使用 switchToLatest这个参数来获取最后一个信号,也就是我们最后所打印的x,就是map最后发错的这个信号。


###repeat

[[[[[RACSignal createSignal:^RACDisposable *(id subscriber) {

   [subscriber sendNext:@"rac"];
   [subscriber sendCompleted];
   
   return nil;

}]delay:1]repeat]take:3] subscribeNext:^(id x) {

   LxDBAnyVar(x);

} completed:^{

   LxPrintAnything(completed);

}];


repeat,顾名思义,就是重复发送这条消息,当我们在后面添加了delay和take的时候,意思就是每隔1秒发送一次这条消息,发送3次后停止。

###merge – concat – zipWith

RACSignal * signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) {

   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       LxPrintAnything(a);
       [subscriber sendNext:@"a"];
       [subscriber sendCompleted];
   });
   
   return nil;

}];

RACSignal * signalB = [RACSignal createSignal:^RACDisposable *(id subscriber) {

   dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       LxPrintAnything(b);
       [subscriber sendNext:@"b"];
       [subscriber sendCompleted];
   });
   
   return nil;

}];

[[RACSignal merge:@[signalA, signalB]]subscribeNext:^(id x) {

   LxDBAnyVar(x);

}];


我们创建了两个请求,A和B,用GCD的方法A延迟两秒钟,B延迟了3秒钟,我们用merge方法合并了A和B,打印结果为

📍__23-[ViewController merge]_block_invoke_2 + 66🎈 a
📍__23-[ViewController merge]_block_invoke29 + 87🎈 x = a
📍__23-[ViewController merge]_block_invoke_215 + 77🎈 b
📍__23-[ViewController merge]_block_invoke29 + 87🎈 x = b


也就是A和B不管谁发送都会打印x,简单的说就是A和B的打印方法用的是同一个。他们之间关系是独立的,如果A发送失败,B依然会执行。
当我们用concat方法链接A和B之后,意思就是当A执行完了之后才会执行B,他们之间是依赖的关系,如果A发送失败,B也不会执行。
请注意合并(merge)和链接(concat)的区别。
zipWith,当用zipWith链接A和B的时候,只有在A.B每隔都至少发送过一次消息的时候才会执行zipWith的方法,它的返回值是一个集合,也就是数组,同时包含了A和B的打印结果。
zipWith的写法等同于 :

[[RACSignal combineLatestWith:@[signalA, signalB]subscribeNext:^(id x) {

    LxDBAnyVar(x);
}];
亦或者

[[RACSignal combineLatest:@[signalA, signalB]]subscribeNext:^(id x) {

    LxDBAnyVar(x);
}];

但是使用`combineLatest`,可以再后面添加更多的信号.

##RAC(<#TARGET, …#>) 宏

//button setBackgroundColor:forState:

UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
[self.view addSubview:button];

@weakify(self);

[button mas_makeConstraints:^(MASConstraintMaker *make) {
    
    @strongify(self);
    make.size.mas_equalTo(CGSizeMake(180, 40));
    make.center.equalTo(self.view);
}];

RAC(button, backgroundColor) = [RACObserve(button, selected) map:^UIColor *(NSNumber * selected) {
    
    return [selected boolValue] ? [UIColor redColor] : [UIColor greenColor];
}];

[[button rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(UIButton * btn) {
    
    btn.selected = !btn.selected;
}];

比如Btn的设置背景颜色的属性,OC中并没有button setBackgroundColor:forState:这种方法,我们不能直接设置其选中后的颜色。在RAC中,则可以很简单的改变BTN的背景颜色。不得不说RAC的简单和强大。

做一个秒表

UILabel * label = ({

   UILabel * label = [[UILabel alloc]init];
   label.backgroundColor = [UIColor cyanColor];
   label;

});
[self.view addSubview:label];

@weakify(self);

[label mas_makeConstraints:^(MASConstraintMaker *make) {
@strongify(self);

   make.size.mas_equalTo(CGSizeMake(240, 40));
   make.center.equalTo(self.view);

}];

RAC(label, text) = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] map:^NSString *(NSDate * date) {

   return date.description;

}];


只有这么多代码,我们便可以完美的做一个秒表,是否很cool?


![](media/14915477632416/14915491586630.gif)


当我们大量使用RAC写代码的时候,会把一个个事件封装成一个个信号,通过触发信号,订阅这个信号来返回各种信息。RAC使我们的代码耦合性根底,聚合性更高。
若有不懂得地方可以留言,若有写错的地方,请及时与我联系,可以留言或者Email等。
文本所用的Demo,下载地址 [戳这里](https://github.com/yimouleng/RACDemo).