最近无意间看到一个视频讲的ReactiveObjC, 觉得挺好用的 但听完后只是了解个大概.

在网上找了些文章, 有的写的比较易懂但看完还是没觉得自己能比较好的使用RAC, 有的甚至让我看不下去

 

这两天刚好公司项目交付闲下来, 想自己去啃下官方文档

ReactiveCocoa是一个基于函数响应式编程的OC框架.

那么什么是函数式响应式编程呢?概念我就不讲了 因为我讲的也不一定准确, 大家可以去baidu看看大神们的解释

下面我大概演示下响应式编程的样子

Masonry是比较常见的一个响应式框架, 它的的用法举例如下:

make.centerY.equalTo(self.view).offset(100);

大家注意它的用法, 点号调用一个事件或属性后可以接着点号调用, 这里一个比较明显的函数响应式编程的好处就是我们可以把一些要使用的连贯的或者有先后顺序的调用方法和事件连在一起, 逻辑清晰明了的完成代码.

那么要如何实现这样的调用方式呢?

centerY.equalTo(self.view)这个能执行的话equalTo就必须是一个返回对象的block

下面试试自己来实现这个, 

建一个Person对象,  加上跑步, 走路的方法

Class: Person;  Method: run; walk;

我们拆分成几个步骤来做, 首先实现

[[person run] walk];先跑, 跑累了再走

要实现这样的调用的话, run就必须返回person, 为了还能继续接着这样调用walk也要返回person

好了, 思路就很清晰了, 我们上代码

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

#import <Foundation/Foundation.h>@interface Person : NSObject- (Person *)run;- (Person *)walk;@end

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

 

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

#import "Person.h"@implementation Person- (Person *)run {
    
    NSLog(@"跑步");    
    // 延时2s
    [NSThread sleepForTimeInterval:2];    
    return self;
}- (Person *)walk {
    
    NSLog(@"走路");    
    // 延时2s
    [NSThread sleepForTimeInterval:2];    
    return self;
}@end

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

大家可以看到, 我们run 跟 walk方法都会返回对象本身, 为了延时我加了个延迟2s

我们调用试试

    // 创建对象
    Person *person = [[Person alloc] init];    
    // 尝试调用
    [[person run] walk];

结果如下:

2017-07-21 21:59:30.962 RAC[63284:11390973] 跑步2017-07-21 21:59:33.036 RAC[63284:11390973] 走路

跟预期一致, 我们再来实现person.run().walk();

刚才说了要返回一个返回值是对象的block, 我们来实现下 代码如下:

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

- (Person * (^)())runBlock {
    
    Person * (^block)() = ^() {
        
        NSLog(@"run");        // 延时2s
        [NSThread sleepForTimeInterval:2];        return self;
    };    
    return block;
}- (Person * (^)())walkBlock {
    
    Person * (^block)() = ^() {
        
        NSLog(@"walk");        // 延时2s
        [NSThread sleepForTimeInterval:2];        return self;
    };    
    return block;
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

如果对block使用不熟的同学可以花点时间琢磨下block的结构

我们调用试试看

    // 调用block
    person.runBlock().walkBlock();

结果:

2017-07-22 13:58:01.306 RAC[64288:11757631] run2017-07-22 13:58:03.381 RAC[64288:11757631] walk

好了, 这样我们就自己实现了一个基于函数响应式的小Demo

 

常规情况下, 我们写代码是一般是定义很多个变量和方法,  在不同的状态和业务流程下去改变变量的值或者调用对应的方法.

而RAC采用信号机制来获取当前的, 同时也能直接处理将来要如何修改这些值, 通过利用链式响应编程来书写结构逻辑清晰的代码, 不用我们在不同的地方去给我们属性值做处理, 

 

比如我们要给一个UITextField做监听, 当值改变的时候做一些处理例如打印当前输入的值, 常规用法下我们要让当前控制器或者类遵循textField的代理, 然后把textField的代理指给当前类, 实现代理方法, 代码大概会是这样:

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

@interface ViewController ()<UITextFieldDelegate>@end@implementation ViewController- (void)viewDidLoad {
    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    

    UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 35)];
    
    textField.center          = self.view.center;
    textField.backgroundColor = [UIColor yellowColor];
    textField.delegate        = self;
    
    [self.view addSubview:textField];
}#pragma mark - UITextFieldDelegate Method

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    
    
    NSLog(@"%@", textField.text);    return YES;
}@end

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

或者大家也能用KVO来实现, 当代码比较少的时候这样看起来还比较清晰, 如果当时一个完整的项目呢, 那么多方法要写我们要看看某一个textField事件估计要花一些时间在代码里面去找这个方法, 代码就不是很直观了.

 

那么RAC怎么帮助我们解决这个问题呢,  上面有说过RAC是通过信号来管理的, 那么什么是信号呢?

RACSignal就是这个类, 我们试试自己创建一个信号 首先我们先用Pod导入ReactiveObjC库

pod 'ReactiveObjC', '~>3.0.0'

导入头文件

#import <ReactiveObjC.h>

我们创建一个信号:

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

// 创建一个信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(<RACSubscriber>

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

直接运行看看, 好像什么都没有发生, 怎么回事呢? 我们点击创建新的方法看看他做了什么

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {    return [RACDynamicSignal createSignal:didSubscribe];
}

他给我们返回了一个RACDynamicSignal,  这个是什么呢? 我们点他看看

@interface RACDynamicSignal : RACSignal+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;

原来他是RACSignal的一个子类, 它也重写了createSignal方法, 我们现在实际是调用了他的创建信号的方法. 那我们看看它这个方法都做了什么

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];    return [signal setNameWithFormat:@"+createSignal:"];
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

它创建了一个RACDynamicSignal实例, 然后把didSubscribe复制了一份复制给创建的实例, 然后重命名后就直接返回给我们了.

然后就结束了, 难怪我们什么效果都没有看到

RAC里面有一个很重要的理念: 创建信号必须订阅, 订阅了信号才会被执行.  

没有订阅的信号是冷信号 不会产生任何效果, 订阅信号就从冷信号变成热信号, 就可以执行各种操作.

 

我们看看如何订阅:

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

    // 创建一个信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        NSLog(@"创建一个信号");        return nil;
    }];    
    // 订阅一个信号
    [signal subscribeNext:^(id  _Nullable x) {
        
        NSLog(@"订阅一个信号");
    }];

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

我们运行看看

2017-07-22 15:05:58.760 RAC[65085:12004278] 创建一个信号

创建信号的block执行了,  但是订阅的信号没有执行, 我们看看点开subscribeNext看看为什么

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];    return [self subscribe:o];
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

它首先判断我们的block不会空, 然后创建了一个RACSubscriber订阅者, 并把我们的block给它了

再点subscriber的创建方法看看它做了什么

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
    RACSubscriber *subscriber = [[self alloc] init];

    subscriber->_next = [next copy];
    subscriber->_error = [error copy];
    subscriber->_completed = [completed copy];    return subscriber;
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

它只是创建了一个subscriber实例, 然后把我们的block拷贝给它 还是什么都没有做

我们再看看

[self subscribe:o];

做了什么

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCAssert(NO, @"This method must be overridden by subclasses");    return nil;
}

它做了非空判断, 然后说这个方法必须被子类重写, 这里好像也啥都没干啊 怎么创建信号的block就执行了呢?

大家想想, 我们刚才创建信号的时候, 是不是就是调用的是RACSignal的子类DynamicSignal, 所以这里实际上运行的也是这个DynamicSignal的subscribe方法, 我们去看看

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];    if (self.didSubscribe != NULL) {
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];
        }];

        [disposable addDisposable:schedulingDisposable];
    }    
    return disposable;
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

 

首先它也是先判断是否为空, 然后创建了一个RACCompoundDisposable实例

接着有给我们的subscriber重新赋值, 我们看看这个RACPassthroughSubscriber

// A private subscriber that passes through all events to another subscriber// while not disposed.@interface RACPassthroughSubscriber : NSObject <RACSubscriber>

它是把事件从一个subscriber传递给另外一个subscriber, 所以这里就是它把我们原有的subscriber + 之前创建的signal + disposable加起来组成一个新的subscriber重新赋值给我们的subscriber,  相当于把我们创建的信号跟订阅绑定到一起了

 

接着如果didsubscribe不为空的话, 及继续执行否则直接返回disposable

我们的didsubscriber大家还记得是什么吗? 打印创建信号那段对吧

然后我们看到它创建了一个RACDisposable实例, 但是它用的是一个RACScheduler来创建的

我们看看这个RACScheduler是个啥

/// Schedulers are used to control when and where work is performed.@interface RACScheduler : NSObject

哦 它是一个类似Timer或者dispatch_after的东西, 控制事件在什么时候触发

我们再看看这个subscriptionScheduler

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

+ (RACScheduler *)subscriptionScheduler {    static dispatch_once_t onceToken;    static RACScheduler *subscriptionScheduler;
    dispatch_once(&onceToken, ^{
        subscriptionScheduler = [[RACSubscriptionScheduler alloc] init];
    });    return subscriptionScheduler;
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

它创建了一个RACScheduler单例, 不过是用RACSubscriptionScheduler来初始化的, 我们再看看它

@interface RACSubscriptionScheduler : RACScheduler

是一个RACSchedule的子类, 它重写的初始化和schedule , after...等等方法,  先记下一会看看是否用到了这些重写的方法

这里我们先看看这个子类重写的初始化方法

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

- (instancetype)init {
    self = [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.subscriptionScheduler"];

    _backgroundScheduler = [RACScheduler scheduler];    return self;
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

重命名, 然后给持有的一个RACScheduler对象backgroundScheduler赋值, 我们看看RACScheduler的scheduler做了什么

+ (RACScheduler *)scheduler {    return [self schedulerWithPriority:RACSchedulerPriorityDefault];
}

继续点

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority {    return [self schedulerWithPriority:priority name:@"org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler"];
}

还是看不出来, 继续点

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {    return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}

返回了一个RACTargetQueueScheduler实例, targetQueue是一个dispatch_get_global_queue全局队列

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

/// A scheduler that enqueues blocks on a private serial queue, targeting an/// arbitrary GCD queue.@interface RACTargetQueueScheduler : RACQueueScheduler/// Initializes the receiver with a serial queue that will target the given/// `targetQueue`.////// name        - The name of the scheduler. If nil, a default name will be used./// targetQueue - The queue to target. Cannot be NULL.////// Returns the initialized object.- (instancetype)initWithName:(nullable NSString *)name targetQueue:(dispatch_queue_t)targetQueue;

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

一个类似队列的东西, 看看它的初始化方法

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

- (instancetype)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue {
    NSCParameterAssert(targetQueue != NULL);    if (name == nil) {
        name = [NSString stringWithFormat:@"org.reactivecocoa.ReactiveObjC.RACTargetQueueScheduler(%s)", dispatch_queue_get_label(targetQueue)];
    }

    dispatch_queue_t queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL);    if (queue == NULL) return nil;

    dispatch_set_target_queue(queue, targetQueue);    return [super initWithName:name queue:queue];
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

前面很清晰, 创建了一个队列

看看super的初始化做了什么< 

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

- (instancetype)initWithName:(NSString *)name queue:(dispatch_queue_t)queue {
    NSCParameterAssert(queue != NULL);

    self = [super initWithName:name];

    _queue = queue;#if !OS_OBJECT_USE_OBJC
    dispatch_retain(_queue);#endif

    return self;
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

 http://www.cnblogs.com/zhouxihi/p/7222063.html