简介

神马是RAC?ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。借用RayWenderlich上面的话:

As an iOS developer, nearly every line of code you write is in reaction to some event; a button tap, a received network message, a property change (via Key Value Observing) or a change in user’s location via CoreLocation are all good examples. However, these events are all encoded in different ways; as actions, delegates, KVO, callbacks and others. ReactiveCocoa defines a standard interface for events, so they can be more easily chained, filtered and composed using a basic set of tools.

翻译过来就是:

作为一个iOS开发者,你写的每一行代码几乎都是在响应某个事件,例如按钮的点击,收到网络消息,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来处理,比如action、delegate、KVO、callback等。ReactiveCocoa为事件定义了一个标准接口,从而可以使用一些基本工具来更容易的连接、过滤和组合。

RAC是由 Mattt Thompson 大神开发的,很多开发者对其的评价是开启一个新Objective-C纪元,可见对其评价有多高。
以下是RAC的Github主页:ReactiveCocoa
以及官方给出的用法链接

安装

ReactiveCocoa安装教程我就不说了,用pod安装即可。
本文的Demo可在文章最后下载,在阅读本文的时候,强烈推荐边看Demo边看博文。
项目中加入了ReactiveCocoa 和 DeveloperLx 的打印插件 LxDBAnything
同时加入了键盘相应的第三方IQKeyboardManager,然而没有怎么用到。
以及Masonry,使用方法可以看这篇文章,iOS – Masonry自动布局(Autolayout)

撸代码

第一部分 简单使用

文本框事件

原来我们在使用textFiled的时候我们需要写到

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

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

```objectivec
 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<RACSubscriber> 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<RACSubscriber> 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<RACSubscriber> 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<RACSubscriber> 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?

结束

当我们大量使用RAC写代码的时候,会把一个个事件封装成一个个信号,通过触发信号,订阅这个信号来返回各种信息。RAC使我们的代码耦合性根底,聚合性更高。

若有不懂得地方可以留言,若有写错的地方,请及时与我联系,可以留言或者Email等。

文本所用的Demo,下载地址 戳这里.


丶伊眸冷

静水流深,沧笙踏歌;三生阴晴圆缺,一朝悲欢离合。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

我不是机器人*