iOS 开源库源码分析之ReactiveCocoa

栏目: IOS · 发布时间: 6年前

内容简介:此文基于ReactiveCocoa是一个将函数响应式编程范式带入Objective-C的一个开源库,由Objective-C能够得此发展源于大量其他语言的工程师加盟iOS社区,Objective-C第一次被苹果以外的人打磨。

此文基于 ReactiveObjC-3.1.0

ReactiveCocoa是一个将函数响应式编程范式带入Objective-C的一个开源库,由 Josh AbernathyJustin Spahr-Summers 在对GitHub for Mac的开发过程中建立。

Objective-C能够得此发展源于大量其他语言的工程师加盟iOS社区,Objective-C第一次被苹果以外的人打磨。

本文主要介绍什么是函数响应式编程,RAC的概览,冷热信号转换的原理,基本的操作符等

函数式编程

什么是函数式编程?举例:

命令式编程

int a, b, r;
void add_abs() {
    scanf("%d %d", &a, &b);
    r = abs(a) + abs(b);
    printf("%d", r);
}

函数式编程

int add_abs(int a, int b) {
    return abs(a) + abs(b);
}

函数式编程的几个特点

  • 函数是第一公民,可以作为参数和返回值
  • 函数的调用不会修改状态,不会修改全局变量,任何一次调用相同的输入都会返回相同的输出

函数式编程的几个技术

递归

递归的好处就是减少代码

高阶函数

高阶函数是参数或者返回值为函数的函数

pipeline

把函数实例成一个一个的action,然后把一组action放到一个数组中,然后把数据传给这个action list,数据就像一个pipeline一样顺序地被各个函数操作

map & reduce & filter

map是一个高阶函数,一个集合的每一个元素通过给定一个函数进行处理,然后转化为另一个集合。

例如map (toLower) "abcDEFG12!@#" 的结果就是"abcdefg12!@#"

reduce也是一个高阶函数,通过一个combiner函数,让集成中两个元素进行处理,得到结果后再与其余元素进行处理,最后得到一个返回值

例如reduce (+) 0 [1..5]最后的结果是15

filter,高阶函数,集合中每一个元素通过一个predicate函数得到true/false,最后得到一个元素处理结果为true组成的集成

例如filter (isAlpha) "$#!+abcDEF657" 结果是 "abcDEF"

柯里化(curry)

curry 的概念很简单:只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

// JS
var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);
var addTen = add(10);

increment(2);
// 3

addTen(2);
// 12

响应式

响应式编程最开始是来自微软的.Net框架 Reactive Extensions

什么是响应式编程?响应式编程就是用asynchronous data streams 进行编程。比较常听到的是asynchronous event streams,比如click event,event bus等。但这边注重的是data与stream,Reactive Extensions将event延伸为data,Stream就是一个 按时间 排序 的Events(Ongoing events ordered in time)序列。

流可以发送3种不同的事物:一个值(类型不限),一个错误或者一个已完成的信号。 我们只能异步捕获这些发送的事件,即:定义一个函数用于当一个值发送出来时再执行,定义一个函数用于当错误发送出来时执行,定义一个函数用于当完成发送出来时执行。

如图

--a---b-c---d---X---|->

a, b, c, d 都是发送出的值
X 是错误
| 是 'completed' 信号
---> 是时间线

RP本身是建立于观察者模式之上的一种编程范式。对流的 “侦听” 又称为 订阅(subscribing),而定义的函数即为 观察者(observer),流就是 主题(subject, observable)。这是一个典型的观察者模式。

Streams

ReactiveCocoa由两大主要部分组成:signals (RACSignal) 和 sequences (RACSequence)。

signal 和 sequence 都是streams,他们共享很多相同的方法。ReactiveCocoa在功能上做了语义丰富、一致性强的一致性设计:signal是push-driver的stream,sequence是pull-driver的stream。pull-driver是任何时刻我有数据了你都可以获取到,因为数据先存储了,取数据的时间控制在调用者上。push-driver是任何时刻有数据了都会push给调用者,如果你没处理就丢失了。

RACStream是一个抽象类,是不能直接实例化的

Signals

根据上面响应式编程流的概念,信号给他们的订阅者发送三种不同的事件类型:

  • next事件,next从流中提供一个新的值
  • error事件,该事件表示早一个信号正常结束之前发生了一个错误。
  • completed事件:表示信号正常结束,同时也没有其他更多的值添加到流中。

Subjects

一个Subject,在RAC中代表的是RACSubject类,是一种可以被手动控制的信号。Subject可以认为是可变的信号,就像NSMutableArray对于NSArray一样。Subject是很有用的连接非RAC代码到RAC的很有用的工具。

Sequences

sequence,在RAC中代表的是RACSequence类,是一种pull-driven的流。Sequence是一种集合类型,类似NSArray.

RACSubscriber

RACSubscriber是订阅者,所有实现了 RACSubscriber 协议的类都可以作为信号源的订阅者。一次订阅是通过调用-subscribeNext:error:completed产生的。订阅会持有它的signal对象,并且在信号completed或者error的时候释放。

RACScheduler

调度器,是一个串行的信号执行队列,用来执行任务或者传递结果。Schedulers类似于GCD中的队列,但是scheduler支持取消队列(通过disposables),并总是串行执行的。

RACDisposable

清洁工,Disposables常常用来取消对一个信号的订阅。

Commands

command,在RAC中表示的是RACCommand类,可以创建或者订阅一个信号用来响应某些action动作。这可以很方便的来处理App中的用户交互。

基本用法如下

RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    NSLog(@"执行命令");
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [subscriber sendNext:@"请求数据"];
        [subscriber sendCompleted];
        return nil;
    }];
}];
[command.executionSignals subscribeNext:^(id x) {
    NSLog(@"signal %@",x);
    [x subscribeNext:^(id x) {
        NSLog(@"value %@",x);
    }];
}];
[command execute:@1];
// output: 执行命令 - signal <RACDynamicSignal: 0x608000227280> - value 请求数据

首先创建一个RACCommand,signalBlock是需要返回一个信号的,可以input(execute传入的值)来返回不同signal。在执行execute的时候会调用RACCommand的_signalBlock,并且把block返回的冷信号通过connection转换为热信号,然后把热信号加入_activeExecutionSignals,这样订阅command.executionSignals(_activeExecutionSignals数组转换的高阶信号)里面收到的信号就可以获取_signalBlock冷信号的值了。

UIButton这里有一个常用的用法可以方便的接收touch事件,如下

self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
    NSLog(@"button was pressed!");
    return [RACSignal empty];
}];

那是因为setRac_command:方法里面先addTarget:action:forControlEvents,然后action里面的实现是self.button.rac_command 调用execute:,这样touch事件发出时RACCommand的signal就能收到回调了。

Connections

在提到RACMulticastConnection的时候需要说一下冷信号和热信号的概念。

  • Hot Observable是主动的,尽管你并没有订阅事件,但是它会时刻推送,就像鼠标移动;而Cold Observable是被动的,只有当你订阅的时候,它才会发布消息。

  • Hot Observable可以有多个订阅者,是一对多,集合可以与订阅者共享信息;而Cold Observable只能一对一,当有不同的订阅者,消息是重新完整发送。

这里可以看 细说ReactiveCocoa的冷信号与热信号(一) 的例子

RACSignal家族中,RACSubject就是热信号,除此还有RACReplaySubject,RACBehaviorSubject,RACGroupedSignal;

RACSubject是继承自RACSignal,并且它还遵守RACSubscriber协议。这就意味着它既能订阅信号,也能发送信号。在RACSubject里面有一个NSMutableArray数组,里面装着该信号的所有订阅者。

RACSignal就是冷信号,除此还有RACEmptySignal,RACReturnSignal,RACDynamicSignal,RACErrorSignal,RACChannelTerminal。

根据RACSignal订阅和发送信号的流程,我们可以知道,每订阅一次冷信号RACSignal,就会执行一次didSubscribe的block。这个时候就是可能出现问题的地方。如果RACSignal是被用于网络请求,那么在didSubscribe block里面会被重复的请求。

如何做到信号只执行一次didSubscribe block,最重要的一点是RACSignal冷信号只能被订阅一次。由于冷信号只能一对一,那么想一对多就只能交给热信号去处理了。这时候就需要把冷信号转换成热信号。

冷信号转换成热信号需要用到RACMulticastConnection 这个类。

RACMulticastConnection最主要的是保存了两个信号,一个是暴露给外部的类型为RACSubject的signal属性,一个是内部的sourceSignal(RACSignal类型)。

用sourceSignal去发送信号,内部再用RACSubject去订阅sourceSignal,然后RACSubject会把sourceSignal的信号值依次发给它的订阅者们。

RACSignal的订阅过程

RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@1];
    [subscriber sendCompleted];
    return nil;
}];
[signal subscribeNext:^(id x) {
    NSLog(@"x%@",x);
}];
  • 首先RACSignal调用createSignal方法创建实例,这里实际是通过RACDynamicSignal子类
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}

实际就是给_didSubscribe变量赋值执行订阅操作需要执行的 block


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Spark技术内幕

Spark技术内幕

张安站 / 机械工业出版社 / 2015-9-1

Spark是不断壮大的大数据分析解决方案家族中备受关注的新增成员。它不仅为分布式数据集的处理提供一个有效框架,而且以高效的方式处理分布式数据集。它支持实时处理、流处理和批处理,提供了AllinOne的统一解决方案,使得Spark极具竞争力。 本书以源码为基础,深入分析Spark内核的设计理念和架构实现,系统讲解各个核心模块的实现,为性能调优、二次开发和系统运维提供理论支持;本文最后以项目实战......一起来看看 《Spark技术内幕》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试