最近无意间看到一个视频讲的 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
好了, 思路就很清晰了, 我们上代码
- #import
- @interface Person: NSObject
- - (Person * ) run; - (Person * ) walk;
- @end
- #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
我们调用试试
- // 创建对象
- 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, 我们来实现下 代码如下:
- - (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 的结构
我们调用试试看
- // 调用block
- person.runBlock().walkBlock();
结果:
- 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 的代理指给当前类, 实现代理方法, 代码大概会是这样:
- @interface ViewController()
- @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 库
- pod 'ReactiveObjC',
- '~>3.0.0'
导入头文件
- #import
我们创建一个信号:
- // 创建一个信号
- RACSignal * signal = [RACSignal createSignal: ^RACDisposable * _Nullable(id _Nonnull subscriber) {
- NSLog(@"创建一个信号");
- return nil;
- }];
直接运行看看, 好像什么都没有发生, 怎么回事呢? 我们点击创建新的方法看看他做了什么
- + (RACSignal * ) createSignal: (RACDisposable * ( ^ )(id subscriber)) didSubscribe {
- return [RACDynamicSignal createSignal: didSubscribe];
- }
他给我们返回了一个 RACDynamicSignal, 这个是什么呢? 我们点他看看
- @interface RACDynamicSignal: RACSignal
- + (RACSignal * ) createSignal: (RACDisposable * ( ^ )(id subscriber)) didSubscribe;
原来他是 RACSignal 的一个子类, 它也重写了 createSignal 方法, 我们现在实际是调用了他的创建信号的方法. 那我们看看它这个方法都做了什么
- + (RACSignal * ) createSignal: (RACDisposable * ( ^ )(id subscriber)) didSubscribe {
- RACDynamicSignal * signal = [[self alloc] init];
- signal - >_didSubscribe = [didSubscribe copy];
- return [signal setNameWithFormat: @"+createSignal:"];
- }
它创建了一个 RACDynamicSignal 实例, 然后把 didSubscribe 复制了一份复制给创建的实例, 然后重命名后就直接返回给我们了.
然后就结束了, 难怪我们什么效果都没有看到
RAC 里面有一个很重要的理念: 创建信号必须订阅, 订阅了信号才会被执行.
没有订阅的信号是冷信号 不会产生任何效果, 订阅信号就从冷信号变成热信号, 就可以执行各种操作.
我们看看如何订阅:
- // 创建一个信号
- RACSignal * signal = [RACSignal createSignal: ^RACDisposable * _Nullable(id _Nonnull subscriber) {
- NSLog(@"创建一个信号");
- return nil;
- }];
- // 订阅一个信号
- [signal subscribeNext: ^(id _Nullable x) {
- NSLog(@"订阅一个信号");
- }];
我们运行看看
- 2017 - 07 - 22 15 : 05 : 58.760 RAC[65085 : 12004278]创建一个信号
创建信号的 block 执行了, 但是订阅的信号没有执行, 我们看看点开 subscribeNext 看看为什么
- - (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 的创建方法看看它做了什么
- + (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];
做了什么
- - (RACDisposable * ) subscribe: (id) subscriber {
- NSCAssert(NO, @"This method must be overridden by subclasses");
- return nil;
- }
它做了非空判断, 然后说这个方法必须被子类重写, 这里好像也啥都没干啊 怎么创建信号的 block 就执行了呢
大家想想, 我们刚才创建信号的时候, 是不是就是调用的是 RACSignal 的子类 DynamicSignal, 所以这里实际上运行的也是这个 DynamicSignal 的 subscribe 方法, 我们去看看
- - (RACDisposable * ) subscribe: (id) 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
- // A private subscriber that passes through all events to another subscriber
- // while not disposed.
- @interface RACPassthroughSubscriber: NSObject
它是把事件从一个 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
- + (RACScheduler * ) subscriptionScheduler {
- static dispatch_once_t onceToken;
- static RACScheduler * subscriptionScheduler;
- dispatch_once( & onceToken, ^{
- subscriptionScheduler = [[RACSubscriptionScheduler alloc] init];
- });
- return subscriptionScheduler;
- }
它创建了一个 RACScheduler 单例, 不过是用 RACSubscriptionScheduler 来初始化的, 我们再看看它
- @interface RACSubscriptionScheduler: RACScheduler
是一个 RACSchedule 的子类, 它重写的初始化和 schedule , after... 等等方法, 先记下一会看看是否用到了这些重写的方法
这里我们先看看这个子类重写的初始化方法
- - (instancetype) init {
- self = [super initWithName: @"org.reactivecocoa.ReactiveObjC.RACScheduler.subscriptionScheduler"];
- _backgroundScheduler = [RACScheduler scheduler];
- return self;
- }
重命名, 然后给持有的一个 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 全局队列
- /// 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;
一个类似队列的东西, 看看它的初始化方法
- - (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 的初始化做了什么 <
- - (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;
- }
保存了这个队列, 就结束了
还记得到哪里了吗, 我把代码再贴下
- - (RACDisposable * ) subscribe: (id) 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 中的方法
- - (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 的实例, 我们先看看如果为空要怎么样
- - (RACDisposable * ) schedule: (void( ^ )(void)) block {
- NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd));
- return nil;
- }
啥都没干, 看起来这里我们就要期待它一定不为空了, 不然我们我们辛辛苦苦找到一个执行的地方就又白找了
那么它到底是不是空呢, 我们先看看它的定义
- /// 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 方法
- + (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
- RACDisposable * innerDisposable = self.didSubscribe(subscriber); [disposable addDisposable: innerDisposable];
我们注意第一句话, 这里就执行了 didSubscribe 并把返回值赋给了一个 RACDisposable
记得 didSubscribe 里面有什么吗? 对就是打印创建信号的那个 block
到这里我们就看到为什么前面创建信号的时候没有调用那里的 block, 原来是订阅的这个地方调用的.
所以创建信号的 block 要订阅的时候才会去执行
不过好像到这里都没有去执行我们订阅的 block, 只是把信号跟订阅捆绑到了一起.
那么要怎么执行订阅的 block 呢? 不知道大家注意到没, 我们订阅用的是 subscribeNext, 看字面意思是订阅然后做去执行
看字面理解我们现在只是完成了订阅的操作, 但没有触发 next
要怎么触发 next 呢
我们可以想想这个触发要什么时候做呢? 目前只有创建和订阅 那么肯定在创建的时候触发对不对
我们看看创建信号的 block 里面的一个 id 格式的参数 subscriber, 不过它有实现一个协议 RACSubscriber
我们看看里面有什么
- @protocol RACSubscriber@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
- // 创建一个信号
- RACSignal * signal = [RACSignal createSignal: ^RACDisposable * _Nullable(id _Nonnull subscriber) {
- NSLog(@"创建一个信号");
- // 发送信号
- [subscriber sendNext: @"发送一个信号"];
- return nil;
- }];
- // 订阅一个信号
- [signal subscribeNext: ^(id _Nullable x) {
- NSLog(@"订阅一个信号");
- }];
运行看看:
- 2017 - 07 - 22 17 : 35 : 38.937 RAC[66240 : 12626323]创建一个信号2017 - 07 - 22 17 : 35 : 38.937 RAC[66240 : 12626323]订阅一个信号
订阅的 block 执行了对不对, 那么还有个问题我们发送信号到哪里去了呢
我们把 x 打印看看
- // 创建一个信号
- RACSignal * signal = [RACSignal createSignal: ^RACDisposable * _Nullable(id _Nonnull subscriber) {
- NSLog(@"创建一个信号");
- // 发送信号
- [subscriber sendNext: @"发送一个信号"];
- return nil;
- }];
- // 订阅一个信号
- [signal subscribeNext: ^(id _Nullable x) {
- NSLog(@"订阅一个信号");
- NSLog(@"订阅到的: %@", x);
- }];
我们把订阅信号 block 里面的参数打印看看
- 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 中了
好了, 先写到这里 希望有帮助大家理解创建信号 订阅信号的到底有在做什么.
下次我们再来看具体的使用方法
来源: http://www.cnblogs.com/zhouxihi/p/7222063.html