ReactiveCocoa框架菜鸟入门(三)——信号(Signal)与订阅者(Subscriber)

本文主要是介绍ReactiveCocoa框架菜鸟入门(三)——信号(Signal)与订阅者(Subscriber),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

写在开始前

传统的编程思想,大概是用户产生某个事件,然后得到相应的参数,传入事先已经实现的方法中,处理完成后把结果在UI界面上反馈出来。ReactiveCocoa框架中大量的使用了block,这意味着,很多block内的代码,是在将来某一个合适的时刻被执行的。如果你看到block里某个参数并没有被赋值,也没有传入参数,不要奇怪,程序运行到这里的时候还不会执行这个block,至于等到需要执行block的时候,会有参数传入的。这是新手在使用block时非常容易产生的误区,如果没有理解这一点,在看代码的时候会产生相当大的麻烦。

信号(Singal)

ReactiveCocoa最基本也是最关键的一个概念叫做信号(Signal)。官方给出的文档中对于信号如此定义:

[RACSignal] is a push-driven stream with a focus on asynchronous event 
delivery through subscriptions

当然这个解释是非常抽象的,一会儿再谈。
个人感觉,linyawen的博客中对信号的解释非常生动形象

信号是数据流,可以被绑定和传递。可以把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。

请牢牢记住“信号就像一个水龙头”这个概念。它允许各种各样的数据从它这里流过。一个信号可以捕捉当前和未来的值。

订阅者/接收者(Subscriber)

回到最初的官方给出的signal的定义,push-driven stream表示,signal是一个由推动力驱动的数据流。即产生一个数据就push一次并进行一系列处理。反过来说,pull-driven表示处理完一个数据才会要求产生一个数据。

对于一个signal来说,刚刚创建的时候,它还是一个冷信号(Cold signal),只有在有了订阅者(Subscriber)之后,才会变为热信号(Hot signal)。订阅者就好比水龙头最下方的水盆,只有放好了水盆,水龙头才能打开。不然,水(value)不都浪费了么?

如果目前对于信号和订阅者还不了解,这是正常情况。接下来通过代码一起了解他们的工作机制。

初窥ReactiveCocoa

ReactiveCocoa的框架的安装已经在前文谈过,这里不再细讲。具体操作方法参见这里。

创建一个空白的工程,从storyboard里面拖入一个UITextField,不妨命名为searchText。
在viewDidLoad方法里面添加以下代码:

[self.searchText.rac_textSignal subscribeNext: ^(id text){NSLog(@"%@", text);}];

运行一下程序,在textfield中输入“hello”。输出结果应该是这样的

2015-05-28 14:55:57.785 TwitterInstant[716:114961] h
2015-05-28 14:55:58.135 TwitterInstant[716:114961] he
2015-05-28 14:55:58.303 TwitterInstant[716:114961] hel
2015-05-28 14:55:58.415 TwitterInstant[716:114961] hell
2015-05-28 14:55:58.663 TwitterInstant[716:114961] hello

可以发现,每一次textfield中文字发生变化,都执行了block中的代码。
一切脱离原理讲效果的行为都是耍流氓。相信任何第一次接触这个框架的读者看到这里一定是一头雾水。很可能你会问这些问题:

为什么参数text就是textfield中的内容而不是其他的任何信息?
为什么每次textfield中内容发生变化,block都被执行?
subscribeNext方法是什么意思?
前文所说的信号、订阅者具体指什么?
下面就是时候看看具体的原理了。千万不要着急学习更多的知识,有时候打好基础,彻底理解一个框架的原理对以后的深入学习更有帮助。请记住这四个问题,如果你能不假思索的回答出来,那么就可以开始进一步的学习了。

ReactiveCocoa的基本工作原理

不妨把上述代码自己敲一遍。searchText后面输入.r的时候已经会出现Xcode的智能提示了。
这里写图片描述

这里的rac_textSignal就是一个信号。执行了subscribeNext方法后,这个信号被订阅(水龙头下面摆好水盆了)。每当有新的数据(textfiled中内容)出现,信号就捕捉到这个值。把这个值传入block中,并且执行block里的代码,于是就打印出了我们看到的数据。为了搞懂为什么新数据出现时,block被调用,我们需要看看subscribeNext方法的具体实现。

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {NSCAssert(NO, @"This method must be overridden by subclasses");return nil;
}- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {NSCParameterAssert(nextBlock != NULL);RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];return [self subscribe:o];
}- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock {NSCParameterAssert(nextBlock != NULL);NSCParameterAssert(completedBlock != NULL);RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:completedBlock];return [self subscribe:o];
}

这里从源代码中简单的节选了几个方法的实现。不难发现,subscribeNext是一个方法簇,除了不带block的那个方法(第一个方法)之外,每一个方法中都创建了一个RACSubscriber(订阅者)对象。subscribeNext方法返回一个RACDisposable(销毁者)对象,这个对象可以用来销毁一个信号,大部分时候没有必要这么做。因为没有被持有的信号会被自动释放。

看到这里,有一个问题的答案已经很明显了:谁是订阅者(subscriber)?
很显然,我们不可能像找到signal那样说rac_textSignal就是一个信号对象,但是订阅者对象就藏在subscribeNext方法中。任何简化的不完全的subscribeNext方法都可以拓展成一个完整的subscribeNext方法:

self.searchText.rac_textSignal subscribeNext:^(id x) {<#code#>} error:^(NSError *error) {<#code#>} completed:^{<#code#>}```
而这里我们指定的三个block将会被用来构建一个订阅者(subscriber)。信号(Signal)和订阅者(Subscriber)还记得第一个demo的四个问题么?回想一下,是不是已经解决问题三和四了呢?为了弄明白前两个问题,我们来看一看信号的工作原理。rac_textSignal是一个已经被创建好的信号,为了不是一般性,我们自己创建一个信号:使用createSignal方法
  • (RACSignal *)createSignal{
    return [RACSignal createSignal:^RACDisposable *(id subscriber) {
    NSLog(@”signal created”);
    return nil;
    }];
    }

有了这个方法,我们就可以创建一个最简单的自定义的信号了。在viewDidLoad方法中加入这样一行代码:

RACSignal *signal = [self createSignal];

运行程序看一看效果吧。并没有什么卵用。。。。。。。这是必然的。说得形象一点,我们搞出来一个水龙头,但是下面还没有放上盆子。说得专业一点,这还是一个冷信号(Cold signal)。还没有被订阅(Subscribe)。归根结底,我们不了解这个createSignal方法的实现原理。 
Ok,按住command键点击方法名,跳过去看看createSignal到底是个啥玩意儿。
  • (RACSignal )createSignal:(RACDisposable (^)(id subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
    }
这个方法有一个blockblock需要一个RACSubscriber(订阅者)对象作为传入的参数,会传出一个销毁者(RACDisposable)对象。block名叫做didSubscribe,显然这个block会在block被subscribe之后触发。现在明白冷信号是什么意思了么?没有订阅者(Subscriber)的时候,用于创建signalblock都没有被执行,这个信号当然没有任何卵用。在viewDidLoad方法中再加一行代码呢:

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


即使只是一个空的block,但是因为signal现在被订阅(Subscribe)了,之前的名为didSubscribe的block就被执行了。此时会输出

2015-05-28 15:36:42.661 TwitterInstant[823:253903] signal created


修改一下刚刚的代码,增加一个NSLog的功能。修改后的subscribeNext方法应该像这样:

RACSignal *signal = [self createSignal];
[signal subscribeNext:^(id x) {
NSLog(@”aaa”);
}];


RACSignal *signal = [self createSignal];[signal subscribeNext:^(id x) {NSLog(@"aaa");}];
1
2
3
4
1
2
3
4
运行程序。Oh,Shit!依然没有任何卵用。说好的会执行subscribeNext的block内的代码呢?aaa怎么没有打出来? 
其实,仔细一想,这样的结果反而是合理的。我们之前讨论的信号(Signal)被订阅(Subscribe)之后,信号这个水龙头的阀门被打开,水流过信号,落入盆子里。可是为啥落入盆子里就会调用subscribeNext的block中的代码?文章的最初写了,block只是一段预先定义好的代码,在将来的某个时间被调用。可我们至今没有在任何代码中看到这样的调用。我们还是再好好看一看订阅者(Subscriber)吧。细说订阅者(Subscriber)之前我们简单的看了订阅者(Subscriber)对象的工作原理:在subscribeNext方法中我们创建了一个订阅者(Subscriber)对象。这个对象是怎么被创建的呢?关键在于那个出现了无数次的subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed方法:
  • (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。在订阅者的创建过程中,对这三个进行了赋值。等待在将来的某一刻被调用。这就好比这个盆子(订阅者)有许多功能,对于不同的滴水状态,具备不同的处理方法。同时订阅者(Subscriber)对象还对外提供了三个方法的调用接口:
  • (void)sendNext:(id)value;
  • (void)sendError:(NSError *)error;
  • (void)sendCompleted;
以- (void)sendNext:(id)value方法为例看一看它的实现:
  • (void)sendNext:(id)value {
    @synchronized (self) {
    void (^nextBlock)(id) = [self.next copy];
    if (nextBlock == nil) return;

    nextBlock(value);
    

    }
    }

- (void)sendNext:(id)value {@synchronized (self) {void (^nextBlock)(id) = [self.next copy];if (nextBlock == nil) return;nextBlock(value);}
}
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
其实最核心的功能即时调用了自己的nextBlock并传入相应的参数而已。那么订阅者(Subscriber)对象的这三个方法什么时候被调用呢?又应该调用哪一个呢?答案是:由信号决定。试想一下:水龙头告诉底下的盆子:“我要滴水了,你存一下吧。”。这就是让订阅者(盆子)执行nextBlcok。如果水龙头说:“我滴完水了,你可以歇一会了。”这就是让订阅者(盆子)执行completeBlock。如果水龙头说:“我滴不了水了,坏掉了,你可以歇一会了。”这就是让订阅者(盆子)执行errorBlock。显然,在一个信号(Signal)的生命周期中,可以发送无数次next事件,和唯一一次complete或者error事件。改造一下最初创造signal的方法:
  • (RACSignal *)createSignal{
    return [RACSignal createSignal:^RACDisposable *(id subscriber) {
    NSLog(@”signal created”);
    [subscriber sendNext:nil];
    return nil;
    }];
    }
    “`

再次运行,就会发现aaa已经被打印出来了。关键就在于这一次我们调用了订阅者(Subscriber)的sendNext方法。

说了这么多,回头看看问题一。重点在于这个rac_textSignal。如果有兴趣可以看看它的实现。其实本质上还是监听了textfield的text属性。当它发生变化时,就把它的值传出来。至于为什么每次传出的都是text值,这是由这个signal的创建方法决定的。

至于问题二,我们执行了subscribeNext方法创建了一个订阅者(Subscriber),这个订阅者的nextBlcok方法已经被赋值。而rac_textSignal这个信号的实现中,在每次text发生变化的时候,就会调用订阅者的sendNext方法,从而调用nextBlcok中的代码。

这就是一个信号(Signal)与订阅者(Subscriber)的简单实用。当然,ReactiveCocoa框架的功能远不止这些。下一章,我们来讨论一下更多的关于信号(Signal)的使用方法——《ReactiveCocoa框架菜鸟入门——信号(Signal)详解》

转载 bestswifter 的博客:http://blog.csdn.net/abc649395594/article/details/46123379

这篇关于ReactiveCocoa框架菜鸟入门(三)——信号(Signal)与订阅者(Subscriber)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/492912

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

数论入门整理(updating)

一、gcd lcm 基础中的基础,一般用来处理计算第一步什么的,分数化简之类。 LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; } <pre name="code" class="cpp">LL lcm(LL a, LL b){LL c = gcd(a, b);return a / c * b;} 例题:

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

【IPV6从入门到起飞】5-1 IPV6+Home Assistant(搭建基本环境)

【IPV6从入门到起飞】5-1 IPV6+Home Assistant #搭建基本环境 1 背景2 docker下载 hass3 创建容器4 浏览器访问 hass5 手机APP远程访问hass6 更多玩法 1 背景 既然电脑可以IPV6入站,手机流量可以访问IPV6网络的服务,为什么不在电脑搭建Home Assistant(hass),来控制你的设备呢?@智能家居 @万物互联

poj 2104 and hdu 2665 划分树模板入门题

题意: 给一个数组n(1e5)个数,给一个范围(fr, to, k),求这个范围中第k大的数。 解析: 划分树入门。 bing神的模板。 坑爹的地方是把-l 看成了-1........ 一直re。 代码: poj 2104: #include <iostream>#include <cstdio>#include <cstdlib>#include <al

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

MySQL-CRUD入门1

文章目录 认识配置文件client节点mysql节点mysqld节点 数据的添加(Create)添加一行数据添加多行数据两种添加数据的效率对比 数据的查询(Retrieve)全列查询指定列查询查询中带有表达式关于字面量关于as重命名 临时表引入distinct去重order by 排序关于NULL 认识配置文件 在我们的MySQL服务安装好了之后, 会有一个配置文件, 也就

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

ZooKeeper 中的 Curator 框架解析

Apache ZooKeeper 是一个为分布式应用提供一致性服务的软件。它提供了诸如配置管理、分布式同步、组服务等功能。在使用 ZooKeeper 时,Curator 是一个非常流行的客户端库,它简化了 ZooKeeper 的使用,提供了高级的抽象和丰富的工具。本文将详细介绍 Curator 框架,包括它的设计哲学、核心组件以及如何使用 Curator 来简化 ZooKeeper 的操作。 1