函数响应式编程及ReactiveObjC学习笔记 (-)

2019-10-15 14:39:41 浏览数 (3)

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

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

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

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

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

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

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

代码语言:javascript复制
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

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

代码语言:javascript复制
#import <Foundation/Foundation.h>

@interface Person : NSObject

- (Person *)run;
- (Person *)walk;

@end
代码语言:javascript复制
#import "Person.h"

@implementation Person

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

@end

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

我们调用试试

代码语言:javascript复制
    // 创建对象
    Person *person = [[Person alloc] init];
    
    // 尝试调用
    [[person run] walk];

结果如下:

代码语言:javascript复制
2017-07-21 21:59:30.962 RAC[63284:11390973] 跑步
2017-07-21 21:59:33.036 RAC[63284:11390973] 走路

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

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

代码语言:javascript复制
- (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;
}

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

我们调用试试看

代码语言:javascript复制
    // 调用block
    person.runBlock().walkBlock();

结果:

代码语言:javascript复制
2017-07-22 13:58:01.306 RAC[64288:11757631] run
2017-07-22 13:58:03.381 RAC[64288:11757631] walk

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

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

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

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

代码语言:javascript复制
@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

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

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

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

代码语言:javascript复制
pod 'ReactiveObjC', '~>3.0.0'

导入头文件

代码语言:javascript复制
#import <ReactiveObjC.h>

我们创建一个信号:

代码语言:javascript复制
// 创建一个信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        NSLog(@"创建一个信号");
        return nil;
    }];

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

代码语言:javascript复制
  (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
}

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

代码语言:javascript复制
@interface RACDynamicSignal : RACSignal

  (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;

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

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

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

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

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

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

我们看看如何订阅:

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

我们运行看看

代码语言:javascript复制
2017-07-22 15:05:58.760 RAC[65085:12004278] 创建一个信号

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

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

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

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

代码语言:javascript复制
  (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;
}

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

我们再看看

[self subscribe:o];

做了什么

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

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

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

代码语言:javascript复制
- (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;
}

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

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

代码语言:javascript复制
// 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是个啥

代码语言:javascript复制
/// Schedulers are used to control when and where work is performed.
@interface RACScheduler : NSObject

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

我们再看看这个subscriptionScheduler

代码语言:javascript复制
  (RACScheduler *)subscriptionScheduler {
    static dispatch_once_t onceToken;
    static RACScheduler *subscriptionScheduler;
    dispatch_once(&onceToken, ^{
        subscriptionScheduler = [[RACSubscriptionScheduler alloc] init];
    });

    return subscriptionScheduler;
}

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

代码语言:javascript复制
@interface RACSubscriptionScheduler : RACScheduler

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

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

代码语言:javascript复制
- (instancetype)init {
    self = [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.subscriptionScheduler"];

    _backgroundScheduler = [RACScheduler scheduler];

    return self;
}

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

代码语言:javascript复制
  (RACScheduler *)scheduler {
    return [self schedulerWithPriority:RACSchedulerPriorityDefault];
}

继续点

代码语言:javascript复制
  (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority {
    return [self schedulerWithPriority:priority name:@"org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler"];
}

还是看不出来, 继续点

代码语言:javascript复制
  (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全局队列

代码语言:javascript复制
/// 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;

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

代码语言:javascript复制
- (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];
}

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

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

代码语言:javascript复制
- (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;
}

保存了这个队列, 就结束了

还记得到哪里了吗, 我把代码再贴下

代码语言:javascript复制
- (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;
}

现在到这个schedule了, 我们看看它做了什么, 注意哦这个时候要去看RACScheduler的子类RACSubscriptionScheduler中的方法

代码语言:javascript复制
- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != NULL);

    if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];

    block();
    return nil;
}

看到了吗 block(), 是不是从来没有觉得这对括号这么可爱的, 我们看着这么半天终于看到一个执行block的地方了

先不急, 我们看看它之前的代码

首先判断block非空, 然后如果RACScheduler.currentScheduler为空的话, 就让backgroundscheduler去调用block

这个backgroundscheduler看菜我们有看是一个RACScheduler的实例, 我们先看看如果为空要怎么样

代码语言:javascript复制
- (RACDisposable *)schedule:(void (^)(void))block {
    NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd));
    return nil;
}

啥都没干, 看起来这里我们就要期待它一定不为空了, 不然我们我们辛辛苦苦找到一个执行的地方就又白找了

那么它到底是不是空呢, 我们先看看它的定义

代码语言:javascript复制
/// The current scheduler. This will only be valid when used from within a
/// -[RACScheduler schedule:] block or when on the main thread.
  (nullable RACScheduler *)currentScheduler;

说是只要在主线程, 或者调用过[RACScheduler schedule]方法就不为空

那么我们现在是在那个线程呢? 还记得吗 我们刚才创建了一个全局队列, 那么有没有切换队列呢

好像没有, 对吧, 没关系我们看看这个currentScheduler的setter方法

代码语言:javascript复制
  (RACScheduler *)currentScheduler {
    RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
    if (scheduler != nil) return scheduler;
    if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;

    return nil;
}

看到没, 它首先创建了一个RACScheduler实例, 用的NSThread的threadDIctonary去找RACScheduleCurrentScheduleKey

如果找到了就在返回这个队列, 否则如果是主线程就返回主线程队列,

我们当前并没有切换队里, 这里应该是给其他情况下使用的

好了, 我们再看看执行的是什么block

代码语言:javascript复制
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];

我们注意第一句话, 这里就执行了didSubscribe并把返回值赋给了一个RACDisposable

记得didSubscribe里面有什么吗? 对就是打印创建信号的那个block

到这里我们就看到为什么前面创建信号的时候没有调用那里的block, 原来是订阅的这个地方调用的.

所以创建信号的block要订阅的时候才会去执行

不过好像到这里都没有去执行我们订阅的block, 只是把信号跟订阅捆绑到了一起.

那么要怎么执行订阅的block呢? 不知道大家注意到没, 我们订阅用的是subscribeNext, 看字面意思是订阅然后做去执行

看字面理解我们现在只是完成了订阅的操作, 但没有触发next

要怎么触发next呢?

我们可以想想这个触发要什么时候做呢? 目前只有创建和订阅 那么肯定在创建的时候触发对不对

我们看看创建信号的block里面的一个id格式的参数subscriber, 不过它有实现一个协议RACSubscriber

我们看看里面有什么

代码语言:javascript复制
@protocol RACSubscriber <NSObject>
@required

/// Sends the next value to subscribers.
///
/// value - The value to send. This can be `nil`.
- (void)sendNext:(nullable id)value;

/// Sends the error to subscribers.
///
/// error - The error to send. This can be `nil`.
///
/// This terminates the subscription, and invalidates the subscriber (such that
/// it cannot subscribe to anything else in the future).
- (void)sendError:(nullable NSError *)error;

/// Sends completed to subscribers.
///
/// This terminates the subscription, and invalidates the subscriber (such that
/// it cannot subscribe to anything else in the future).
- (void)sendCompleted;

/// Sends the subscriber a disposable that represents one of its subscriptions.
///
/// A subscriber may receive multiple disposables if it gets subscribed to
/// multiple signals; however, any error or completed events must terminate _all_
/// subscriptions.
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

@end

很清楚对不对, 原来在这里,

那我们试试看发送一个sendNext

代码语言:javascript复制
    // 创建一个信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        NSLog(@"创建一个信号");
        
        // 发送信号
        [subscriber sendNext:@"发送一个信号"];
        
        return nil;
    }];
    
    // 订阅一个信号
    [signal subscribeNext:^(id  _Nullable x) {
        
        NSLog(@"订阅一个信号");
    }];

运行看看:

代码语言:javascript复制
2017-07-22 17:35:38.937 RAC[66240:12626323] 创建一个信号
2017-07-22 17:35:38.937 RAC[66240:12626323] 订阅一个信号

订阅的block执行了对不对, 那么还有个问题我们发送信号到哪里去了呢

我们把x打印看看

代码语言:javascript复制
    // 创建一个信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        NSLog(@"创建一个信号");
        
        // 发送信号
        [subscriber sendNext:@"发送一个信号"];
        
        return nil;
    }];
    
    // 订阅一个信号
    [signal subscribeNext:^(id  _Nullable x) {
        
        NSLog(@"订阅一个信号");
        NSLog(@"订阅到的: %@", x);
    }];

我们把订阅信号block里面的参数打印看看

代码语言:javascript复制
2017-07-22 17:37:09.294 RAC[66282:12633516] 创建一个信号
2017-07-22 17:37:09.295 RAC[66282:12633516] 订阅一个信号
2017-07-22 17:37:09.295 RAC[66282:12633516] 订阅到的: 发送一个信号

打印出来了, 原来是这样传递的

sendNext就把内容传递到了Next的block中了

好了, 先写到这里 希望有帮助大家理解创建信号 订阅信号的到底有在做什么.

下次我们再来看具体的使用方法

0 人点赞