iOS 开源库源码分析之ReactiveCocoa

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

内容简介:此文基于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


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

查看所有标签

猜你喜欢:

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

Game Engine Architecture, Second Edition

Game Engine Architecture, Second Edition

Jason Gregory / A K Peters/CRC Press / 2014-8-15 / USD 69.95

A 2010 CHOICE outstanding academic title, this updated book covers the theory and practice of game engine software development. It explains practical concepts and techniques used by real game studios,......一起来看看 《Game Engine Architecture, Second Edition》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换