这个topic是很久以来多想写的,但是一直没有被迫写,所以这次尝试写一下~
RAC 指的就是 RactiveCocoa ,是 Github 的一个开源框架,能够通过信号提供大量方便的事件处理方案,让我们更简单粗暴地去处理事件,现在分为 ReactiveObjC(OC) 和 ReactiveSwift(swift)。
所以使用的话就只要:
pod 'ReactiveObjC'
RAC可以处理事件的监听,接管了苹果所有的事件机制(addTarget,代理,通知,KVO)
,是一套重量级响应式函数式编程开源框架,它可以帮助我们简单的处理事件。它具有高聚合低耦合的特性。但需要注意Block中的循环引用问题
这里引入了两个问题:什么是响应式 & 什么是函数式?
什么是函数式?
函数式的一个典型是swift那种,那么它的定义是什么呢?
Functional programming is a programming paradigm
1.treats computation as the evaluation of mathematical functions
2.avoids changing-state and mutable data
by wikipedia
从它的定义也可以知道函数式的主要特点:
- 没有使用外部变量,只是使用了传入的参数,类似数学中的函数,真正的是函数的输入映射到输出,只要固定输入,那么每次执行函数的结果都是一样的。
(所以它不怕多线程执行)
那么追求纯函数的理由是什么呢:
-
可缓存性:
var squareNumber = memoize(function(x){ return x*x; });
squareNumber(4);//=> 16
squareNumber(4); // 从缓存中读取输入值为 4 的结果 -
可移植性/自文档化:
纯函数是完全自给自足的,它需要的所有东西都能轻易获得。自给自足:不使用外部数据或者函数,所有函数内部所需要的外部数据都以参数的形式传入。 -
可测试性:
相同输入得到相同的输出 -
合理性(引用透明性):
使用一种叫做“等式推导”(equational reasoning)的技术来分析代码。所谓“等式推导”就是“一对一”替换,有点像在不考虑程序性执行的怪异行为(quirks of programmatic evaluation)的情况下,手动执行相关代码。 -
并行代码:
可以并行运行任意纯函数。因为纯函数根本不需要访问共享的内存,而且根据其定义,纯函数也不会因副作用而进入竞争态
函数是一等公民(first-class citizens)。层级越高,权力越大。很多时候高层的人有权利做的事,下面的人就不能做。
程序世界里,有且仅有这么几种权力: 创建,赋值(赋值给变量),传递(当作参数传递)。
不同的编程语言,函数的权利会不一样。
- 在java语言中,创建/赋值/传递,这些权利object 都具备,function 都不具备。对象可以通过参数传递到另一个对象里,从而两个对象可以互相通信。函数却不行,两个函数想要通信,必须以对象为介质。
- 在js中,函数可以创建/赋值/传递,所以在js中函数是一等公民。函数是 javascript 的主要工作单元。
Programming Paradigm 编程范式是什么?
编程范式(Programming Paradigm)是某种编程语言典型的编程风格或者说是编程方式。一种范式可以在不同的语言中实现,一种语言也可以同时支持多种范式。例如 JavaScript 就是一种多范式的语言。
(以下非原创借鉴写的很好的人哒)
- 几种常用的编程范式:
过程化(命令式)编程
:过程化编程,也被称为命令式编程,应该是最原始的、也是我们最熟悉的一种传统的编程方式。从本质上讲,它是“冯.诺伊曼机“运行机制的抽象,它的编程思维方式源于计算机指令的顺序排列。我们常常写的也就是这种命令式编程。事件驱动编程
:基于事件驱动的程序设计在图形用户界面(GUI)出现很久前就已经被应用于程序设计中,可是只有当图形用户界面广泛流行时,它才逐渐形演变为一种广泛使用的程序设计模式。面向对象编程
:过程化范式要求程序员用按部就班的算法看待每个问题。很显然,并不是每个问题都适合这种过程化的思维方式。这也就导致了其它程序设计范式出现,包括我们现在介绍的面向对象的程序设计范式。面向对象的程序设计包括了三个基本概念:封装性、继承性、多态性。面向对象的程序语言通过类、方法、对象和消息传递,来支持面向对象的程序设计范式。函数式编程
:比起过程化编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
举个例子:
// 命令式
function mysteryFn (nums) {
let squares = []
let sum = 0 // 1. 创建中间变量
for (let i = 0; i < nums.length; i++) {
squares.push(nums[i] * nums[i]) // 2. 循环计算平方
}
for (let i = 0; i < squares.length; i++) {
sum += squares[i] // 3. 循环累加
}
return sum
}
// 以上代码都是 how 而不是 what...
// 函数式
const mysteryFn = (nums) => nums
.map(x => x * x) // a. 平方
.reduce((acc, cur) => acc + cur, 0) // b. 累加
响应式
什么是响应式?这个问题其实之前有篇里面我也讲过,就是如果正常我们写一行代码a = b + c
,此时如果b和c都是1,那么这个时候a得到的就是2,然后如果在这行之后b变为了2,你需要重新执行a = b + c
才能让a更新为3,但是响应式就是当b变为2的时候c自动就可以变成3。
RAC使用
强推:https://www.jianshu.com/p/35a28cf0a22f
终于到了真正的使用啦,每个类都大概看一下怎么用吧~ RAC有现成的KVO以及UI之类的方法,可以直接用例如:
[[button1 rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"button1 clicked");
}];
1. RACSignal
高阶用法欢迎参考:https://www.jianshu.com/p/7620edadcf88
- (void)testSignal {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"block调用时刻:每当有订阅者订阅信号,就会调用block。");
// 2.发送信号
// [subscriber sendNext:@1];
// 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
[subscriber sendError:nil];
return [RACDisposable disposableWithBlock:^{
NSLog(@"信号被销毁");
}];
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"signal touched 1");
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"signal touched 2");
}];
}
上面酱紫如果只是设置了订阅block,外加signal在函数结束的时候没有引用了就会自动销毁,于是输出为:
2020-08-21 23:31:04.311701+0800 Example1[98032:2252660] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-21 23:31:04.311879+0800 Example1[98032:2252660] 信号被销毁
2020-08-21 23:31:04.312061+0800 Example1[98032:2252660] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-21 23:31:04.312176+0800 Example1[98032:2252660] 信号被销毁
这里的信号被销毁
每次subscribe的时候都会调用,其实也就是signal可能对应多个订阅者,默认每个订阅者加入的时候给signal一个机会让它在block里面给订阅者发消息,如果它发了,订阅者的next block就会执行,但无论发了没有,默认都是给signal处理机会以后就会执行RACDisposable dispose
解除已经有过接手信号机会的订阅者。
现在打开[subscriber sendNext:@1];
的注释,那么由于调用了sendNext
,订阅者的订阅block就会被调用,所以signal会被touch~
2020-08-21 23:34:32.705363+0800 Example1[98144:2256354] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-21 23:34:32.705608+0800 Example1[98144:2256354] signal touched 1
2020-08-21 23:34:32.705818+0800 Example1[98144:2256354] 信号被销毁
2020-08-21 23:34:32.706005+0800 Example1[98144:2256354] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-21 23:34:32.706165+0800 Example1[98144:2256354] signal touched 2
2020-08-21 23:34:32.706355+0800 Example1[98144:2256354] 信号被销毁
原理可以参考上面的系列文章里面的第一个,主要是酱紫的:
每个subscriber都遵循RACSubscriber
协议,其实有4个方法:
- (void)sendNext:(nullable id)value;
- (void)sendError:(nullable NSError *)error;
- (void)sendCompleted;
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
error和complete其实对应着我们在订阅的时候传入的block,如果我们send error了,那么error的block就会执行,complete也是:
RACDisposable *dispose = [signal subscribeNext:^(id _Nullable x) {
NSLog(@"signal touched 1");
} error:^(NSError * _Nullable error) {
NSLog(@"signal error");
} completed:^{
NSLog(@"signal completed");
}];
2. RACDisposable
这个就是听起来就知道是做什么系列了,其实是订阅者销毁的时候回触发的block,RACDisposable就是对block的一层包装:
- (instancetype)initWithBlock:(void (^)(void))block {
NSCParameterAssert(block != nil);
self = [super init];
_disposeBlock = (void *)CFBridgingRetain([block copy]);
OSMemoryBarrier();
return self;
}
订阅者取消订阅有两种可能性都会触发:
- 订阅者destroy没有强引用了
- 手动调用了
[disposable dispose]
第一种其实就是为什么我们的signal每次被订阅都会销毁一次,我们如果用一个数组持有一下订阅者,那么就不会触发销毁啦:
- (void)testSignal {
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"block调用时刻:每当有订阅者订阅信号,就会调用block。");
// 2.发送信号
[subscriber sendNext:@1];
[self->subscribers addObject:subscriber];
return [RACDisposable disposableWithBlock:^{
NSLog(@"信号被销毁");
}];
}];
RACDisposable *dispose = [signal subscribeNext:^(id _Nullable x) {
NSLog(@"signal touched 1");
} error:^(NSError * _Nullable error) {
NSLog(@"signal error");
} completed:^{
NSLog(@"signal completed");
}];
// [dispose dispose];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"signal touched 2");
}];
}
输出:
2020-08-22 12:46:15.780694+0800 Example1[3545:41194] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-22 12:46:15.780933+0800 Example1[3545:41194] signal touched 1
2020-08-22 12:46:15.781115+0800 Example1[3545:41194] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-22 12:46:15.781244+0800 Example1[3545:41194] signal touched 2
这个时候就发现dispose没有再调用了~ 那么如果我想让他不通过释放订阅者解除订阅要怎么做呢?打开上面注释的[dispose dispose];
即可手动释放订阅者,输出将变为:
2020-08-22 12:48:55.519454+0800 Example1[3626:43490] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-22 12:48:55.519770+0800 Example1[3626:43490] signal touched 1
2020-08-22 12:48:55.519971+0800 Example1[3626:43490] 信号被销毁
2020-08-22 12:48:55.520135+0800 Example1[3626:43490] block调用时刻:每当有订阅者订阅信号,就会调用block。
2020-08-22 12:48:55.520283+0800 Example1[3626:43490] signal touched 2
注意哦,如果我们sendNext
以后调用了sendComplete
,那么其实sendComplete
里面会给你调用dispose方法解除订阅哒。
3. RACSubject
这个RACSubject其实是继承自RACSignal的,它更像我们以为的信号,就是当 send next 触发的时候,所有订阅者的next block都会被触发:
- (void)testSubject {
RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"next1 :%@", x);
}];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"next2 :%@", x);
}];
NSLog(@"testSubject");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subject sendNext:@"x"];
});
}
输出:
2020-08-22 12:52:43.338328+0800 Example1[3713:45879] testSubject
2020-08-22 12:52:46.338972+0800 Example1[3713:45879] next1 :x
2020-08-22 12:52:46.339484+0800 Example1[3713:45879] next2 :x
原理其实就是它循环了一下所有的订阅者:
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
}];
}
那么如果我想dispose一下肿么破呢?那就要看subscriber协议里面最后一个方法了:
- (void)testSubject {
RACSubject *subject = [RACSubject subject];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"next1 :%@", x);
}];
[subject didSubscribeWithDisposable:[RACCompoundDisposable disposableWithBlock:^{
NSLog(@"disposable block1");
}]];
[subject didSubscribeWithDisposable:[RACCompoundDisposable disposableWithBlock:^{
NSLog(@"disposable block2");
}]];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"next2 :%@", x);
}];
NSLog(@"testSubject");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subject sendNext:@"x"];
});
}
输出:
2020-08-22 12:53:56.902050+0800 Example1[4259:50201] testSubject
2020-08-22 12:53:59.902840+0800 Example1[4259:50201] next1 :x
2020-08-22 12:53:59.903351+0800 Example1[4259:50201] next2 :x
2020-08-22 12:53:59.903802+0800 Example1[4259:50201] disposable block1
2020-08-22 12:53:59.904165+0800 Example1[4259:50201] disposable block2
也就是在signal销毁的时候会触发我们加入的disposable们,并且是按照加入的顺序触发哒。
4. RACReplaySubject
这个其实和subject很类似,区别就是这个是可以先发信号再订阅,也就是新的订阅者订阅的时候,可以拿到之前发过的所有历史信息:
- (void)testReplaySubject {
RACReplaySubject *subject = [RACReplaySubject subject];
[subject sendNext:@"hhh1"];
[subject sendNext:@"hhh2"];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"value received: %@", x);
}];
NSLog(@"finished");
}
输出:
2020-08-22 12:57:29.315784+0800 Example1[4367:53504] value received: hhh1
2020-08-22 12:57:29.316010+0800 Example1[4367:53504] value received: hhh2
2020-08-22 12:57:29.316146+0800 Example1[4367:53504] finished
这个原理其实就是在sendNext的时候把数据存到了一个数组里面,然后订阅者订阅的时候会先把这个数组遍历一次:
- (void)sendNext:(id)value {
@synchronized (self) {
[self.valuesReceived addObject:value ?: RACTupleNil.tupleNil];
if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) {
[self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)];
}
[super sendNext:value];
}
}
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
@synchronized (self) {
for (id value in self.valuesReceived) {
if (compoundDisposable.disposed) return;
[subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)];
}
……
}
}];
[compoundDisposable addDisposable:schedulingDisposable];
return compoundDisposable;
}
5. RACScheduler
Scheduler其实是对GCD的一个封装,主要是做调度的:
- (void)testScheduler {
NSLog(@"started");
[[RACScheduler scheduler] schedule:^{
NSLog(@"当前线程:%@",[NSThread currentThread]);
}];
NSLog(@"finished");
}
输出:
2020-08-22 13:02:05.910901+0800 Example1[4478:57139] started
2020-08-22 13:02:05.911190+0800 Example1[4478:57139] finished
2020-08-22 13:02:05.911413+0800 Example1[4478:57289] 当前线程:<NSThread: 0x600001200d80>{number = 3, name = (null)}
RACScheduler是一个父类,他有很多子类:
我们用到的直接其实是:
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}
@interface RACTargetQueueScheduler : RACQueueScheduler
看RACQueueScheduler的名字也可以知道这个scheduler其实是一个依赖于queue执行的:
- (RACDisposable *)schedule:(void (^)(void))block {
NSCParameterAssert(block != NULL);
RACDisposable *disposable = [[RACDisposable alloc] init];
dispatch_async(self.queue, ^{
if (disposable.disposed) return;
[self performAsCurrentScheduler:block];
});
return disposable;
}
也就是说,它的执行顺序依赖于我们传入的是并行还是串行queue。
我们通过[RACScheduler scheduler]
创建的其实传入的是global queue,也就是一个并行的队列,下面来尝试一下:
- (void)testScheduler {
NSLog(@"started");
[[RACScheduler scheduler] schedule:^{
NSLog(@"当前线程1:%@",[NSThread currentThread]);
sleep(3);
NSLog(@"当前线程1:%@",[NSThread currentThread]);
}];
[[RACScheduler scheduler] schedule:^{
NSLog(@"当前线程2:%@",[NSThread currentThread]);
sleep(3);
NSLog(@"当前线程2:%@",[NSThread currentThread]);
}];
NSLog(@"finished");
}
输出:
2020-08-22 13:09:57.205527+0800 Example1[4663:63879] started
2020-08-22 13:09:57.205864+0800 Example1[4663:63879] finished
2020-08-22 13:09:57.206289+0800 Example1[4663:63989] 当前线程1:<NSThread: 0x6000035a4640>{number = 4, name = (null)}
2020-08-22 13:09:57.206331+0800 Example1[4663:63988] 当前线程2:<NSThread: 0x6000035f07c0>{number = 5, name = (null)}
2020-08-22 13:10:00.207777+0800 Example1[4663:63989] 当前线程1:<NSThread: 0x6000035a4640>{number = 4, name = (null)}
2020-08-22 13:10:00.207825+0800 Example1[4663:63988] 当前线程2:<NSThread: 0x6000035f07c0>{number = 5, name = (null)}
RACScheduler还有其他的获取方式:
- immediateScheduler,立即执行的线程,其实就是在当前线程执行的
- mainThreadScheduler,获取主线程调度器
- currentScheduler,就是获取当前线程调度器,但不会立即执行
- (void)testScheduler {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"当前线程:%@",[NSThread currentThread]);
NSLog(@"started");
[[RACScheduler immediateScheduler] schedule:^{
NSLog(@"当前线程2:%@",[NSThread currentThread]);
sleep(3);
NSLog(@"当前线程2:%@",[NSThread currentThread]);
}];
NSLog(@"finished");
});
}
输出:
2020-08-22 21:26:46.562790+0800 Example1[7651:88680] started
2020-08-22 21:26:46.563918+0800 Example1[7651:88742] 当前线程:<NSThread: 0x6000038905c0>{number = 7, name = (null)}
2020-08-22 21:26:46.564143+0800 Example1[7651:88742] started
2020-08-22 21:26:46.564534+0800 Example1[7651:88742] 当前线程2:<NSThread: 0x6000038905c0>{number = 7, name = (null)}
2020-08-22 21:26:49.569298+0800 Example1[7651:88742] 当前线程2:<NSThread: 0x6000038905c0>{number = 7, name = (null)}
2020-08-22 21:26:49.569719+0800 Example1[7651:88742] finished
如果换成mainThreadScheduler
输出就会是酱紫,不再是同步调用啦:
2020-08-22 21:29:35.556247+0800 Example1[7754:91485] 当前线程:<NSThread: 0x600000b249c0>{number = 4, name = (null)}
2020-08-22 21:29:35.556581+0800 Example1[7754:91485] started
2020-08-22 21:29:35.556826+0800 Example1[7754:91485] finished
2020-08-22 21:29:35.579004+0800 Example1[7754:91405] 当前线程2:<NSThread: 0x600000b61080>{number = 1, name = main}
2020-08-22 21:29:38.579679+0800 Example1[7754:91405] 当前线程2:<NSThread: 0x600000b61080>{number = 1, name = main}
如果换成currentScheduler
是酱紫的,按理说也是异步的,但实际输出是酱紫的:
2020-08-22 21:31:23.592051+0800 Example1[7841:93828] 当前线程:<NSThread: 0x60000340dfc0>{number = 5, name = (null)}
2020-08-22 21:31:23.592302+0800 Example1[7841:93828] started
2020-08-22 21:31:23.592497+0800 Example1[7841:93828] finished
为啥没执行schedule的内容呢?
+ (RACScheduler *)currentScheduler {
RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
if (scheduler != nil) return scheduler;
if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;
return nil;
}
如果当前线程之前没有创建scheduler并且又不是主线程,那么就会return一个nil,自然就不会执行啦。
- scheduler还可以通过优先级来创建,例如酱紫:
- (void)testScheduler {
[[RACScheduler schedulerWithPriority:RACSchedulerPriorityLow] schedule:^{
NSLog(@"scheduler:%@",[RACScheduler currentScheduler]);
NSLog(@"当前线程:%@",[NSThread currentThread]);
}];
}
输出:
2020-08-22 21:35:24.544379+0800 Example1[8014:98226] scheduler:<RACTargetQueueScheduler: 0x600001476a20> org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler
2020-08-22 21:35:24.544993+0800 Example1[8014:98226] 当前线程:<NSThread: 0x600000167900>{number = 5, name = (null)}
RAC优先级其实是对应GCD的优先级的:
typedef enum : long {
RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH,
RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT,
RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW,
RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND,
} RACSchedulerPriority;
不同优先级其实就是对应了gcd不同的queue,注意这里默认是global的queue哈:
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}
Finally,为什么RAC要封装GCD呢?这个其实我感觉是为了更好用和好看,dispatch很多缩进,但是RAC如果可以直接delivery就会好看很多~ 希望有大佬来教学一下这个问题~
6. RACMulticastConnection
如果我们通过signal做网络请求可以酱紫:
- (void)testConn {
RACSignal * signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送网络请求");
sleep(3);
[subscriber sendNext:@"得到网络请求数据"];
return nil;
}];
[signal subscribeNext:^(id x) {
NSLog(@"1 - %@",x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"2 - %@",x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"3 - %@",x);
}];
}
输出:
2020-08-22 21:46:45.095715+0800 Example1[8973:108249] 发送网络请求
2020-08-22 21:46:48.097432+0800 Example1[8973:108249] 1 - 得到网络请求数据
2020-08-22 21:46:48.098200+0800 Example1[8973:108249] 发送网络请求
2020-08-22 21:46:51.099029+0800 Example1[8973:108249] 2 - 得到网络请求数据
2020-08-22 21:46:51.099650+0800 Example1[8973:108249] 发送网络请求
2020-08-22 21:46:54.101352+0800 Example1[8973:108249] 3 - 得到网络请求数据
就可能会重复触发,RACMulticastConnection
就是用于避免这个事儿的:
- (void)testConn {
RACSignal * signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送网络请求");
sleep(3);
[subscriber sendNext:@"得到网络请求数据"];
return nil;
}];
RACMulticastConnection *connect = [signal publish];
[connect.signal subscribeNext:^(id x) {
NSLog(@"1 - %@",x);
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"2 - %@",x);
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"3 - %@",x);
}];
[connect connect];
}
输出:
2020-08-22 21:57:56.231520+0800 Example1[9697:117688] 发送网络请求
2020-08-22 21:57:59.232046+0800 Example1[9697:117688] 1 - 得到网络请求数据
2020-08-22 21:57:59.232525+0800 Example1[9697:117688] 2 - 得到网络请求数据
2020-08-22 21:57:59.232854+0800 Example1[9697:117688] 3 - 得到网络请求数据
它的大概原理是酱紫的,感兴趣的可以看之前的参考:
其实就是conn的signal其实是一个subject,而非之前的RACSignal,所以可以订阅好几个。注意哦,signal订阅的时候是顺序执行的,需要先执行创建时候每次订阅触发的block,之后才能走下一个订阅;而subject比较正常,是可以先订阅的,然后一起等待sendNext的触发
7. RACCommand
这个东西其实我也一直很迷茫是干啥用的~ 参考了:https://www.jianshu.com/p/dc472c644e7b
一般情况下,RACCommand主要用来封装一些请求、事件等,举个例子,我们的tableView在下拉滚动时若想刷新数据需要向接口提供页码或者最后一个数据的ID,我们可以把请求封装进RACCommand里,想要获取数据的时候只要将页码或者ID传入RACCommand里就可以了,同时监控RACCommand何时完成,若完成后将数据加入到tableview的数组就可以了,这是一个平常用的比较多的场景。使用是主要有三个注意点:
- RACCommand必须返回信号,信号可以为空
- RACCommand必须强引用
- RACCommand发送完数据必须发送完成信号
例如酱紫:
-(void)loadInfo{
//input就是控制器中,viewmodel执行command时excute传入的参数
RACCommand * command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
//command必须有信号返回值,如果没有的话可以为[RACSignal empty]
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber)
{
NSMutableDictionary * params = [NSMutableDictionary dictionary];
params[@"build"] = @"3360";
params[@"channel"] = @"appstore";
params[@"plat"] = @"2";
[FYRequestTool GET:@"http://app.bilibili.com/x/banner" parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[subscriber sendNext:responseObject];
//发送完信号必须发送完成信号,否则无法执行
[subscriber sendCompleted];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[subscriber sendError:error];
}];
return [RACDisposable disposableWithBlock:^{
[FYRequestTool cancel];
NSLog(@"这里面可以写取消请求,完成信号后请求会取消");
}];
}];
}];
//必须强引用这个command,否则无法执行
self.command = command;
}
signal如果我们在订阅的block里面去做网络请求,是无法带入一些参数给它的,但是command可以通过execute的参数做区分,然后你可以返回一个signal,在signal内部通过不同input做不同的事情,最后sendNext一下触发订阅者的block告知订阅者并返回拿到的数据:
- (void)testCommand {
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
NSLog(@"create command 1 with input:%@", input);
sleep(3);
NSLog(@"create command 2");
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
// 这里可以拿input去做不同的事情
NSLog(@"signal subscribed 1 with input:%@", input);
sleep(3);
[subscriber sendNext:@"a"];
NSLog(@"signal subscribed 2");
return [RACDisposable disposableWithBlock:^{
NSLog(@"RACDisposable disposable block");
}];
}];
}];
NSLog(@"testCommand");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSString *input = @"执行";
[[command.executionSignals switchToLatest] subscribeNext:^(id _Nullable x) {
NSLog(@"switchToLatest-->%@",x);
}];
[command execute:input];
});
}
输出:
2020-08-22 22:25:33.442545+0800 Example1[10798:139387] testCommand
2020-08-22 22:25:36.443640+0800 Example1[10798:139387] create command 1 with input:执行
2020-08-22 22:25:39.445241+0800 Example1[10798:139387] create command 2
2020-08-22 22:25:39.447601+0800 Example1[10798:139387] executing-->0
2020-08-22 22:25:39.448927+0800 Example1[10798:139387] executing-->1
2020-08-22 22:25:39.449554+0800 Example1[10798:139387] signal subscribed 1 with input:执行
2020-08-22 22:25:42.450867+0800 Example1[10798:139387] switchToLatest-->a
2020-08-22 22:25:42.451391+0800 Example1[10798:139387] signal subscribed 2
2020-08-22 22:25:42.451810+0800 Example1[10798:139387] RACDisposable disposable block
8. bind
bind其实是RACStream的方法,但是很多类都复写了它,例如signal的:
- (RACSignal *)bind:(RACSignalBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
/*
* -bind: should:
*
* 1. Subscribe to the original signal of values.
* 2. Any time the original signal sends a value, transform it using the binding block.
* 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
* 4. If the binding block asks the bind to terminate, complete the _original_ signal.
* 5. When _all_ signals complete, send completed to the subscriber.
*
* If any signal sends an error at any point, send that to the subscriber.
*/
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACSignalBindBlock bindingBlock = block();
__block volatile int32_t signalCount = 1; // indicates self
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
void (^completeSignal)(RACDisposable *) = ^(RACDisposable *finishedDisposable) {
if (OSAtomicDecrement32Barrier(&signalCount) == 0) {
[subscriber sendCompleted];
[compoundDisposable dispose];
} else {
[compoundDisposable removeDisposable:finishedDisposable];
}
};
void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
OSAtomicIncrement32Barrier(&signalCount);
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
RACDisposable *disposable = [signal subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(selfDisposable);
}
}];
selfDisposable.disposable = disposable;
};
@autoreleasepool {
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
id signal = bindingBlock(x, &stop);
@autoreleasepool {
if (signal != nil) addSignal(signal);
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(selfDisposable);
}
}];
selfDisposable.disposable = bindingDisposable;
}
return compoundDisposable;
}] setNameWithFormat:@"[%@] -bind:", self.name];
}
其实就是创建了一个新的signal,然后在这个信号被订阅的时候会执行我们bind时候提供的block,根据我们在bind里面返回的signal是不是空来决定是要addSignal还是addComplete。
注意哦之所以后面signal被触发的时候,可以给subscriber send next主要是因为在addSignal
里面有引用subscriber哦。
所以其实bind主要做的是:
正常来说我们发送信号只有信号的订阅者才会接收到消息,而bind会将信号拦截过滤后发送到新的信号订阅者中。
它的流程是酱紫的:
- 原信号->bind(生成新信号)
- 原信号发送消息->bind Block 进行过滤,然后发送消息到内部一个信号中-> 转发到bind时生成的新信息号中
举个例子:
- (void)testBind {
RACSubject *subject = [RACSubject subject];
RACSignal * signal = [subject bind:^RACSignalBindBlock _Nonnull{
NSLog(@"bind block");
return ^RACSignal *(id _Nullable value, BOOL *stop){
NSLog(@"signal block");
return [RACReturnSignal return:value];
};
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"收到的数据 - %@",x);
}];
[subject sendNext:@"启动自毁程序"];
}
输出:
2020-08-23 08:45:52.755609+0800 Example1[2023:32254] bind block
2020-08-23 08:45:52.756066+0800 Example1[2023:32254] signal block
2020-08-23 08:45:52.756573+0800 Example1[2023:32254] 收到的数据 - 启动自毁程序
在rac里面也有很多地方用到了bind,比如flattenMap
方法,其实就是bind的封装。内部就是bind方法的调用,然后可以传入一个block 作为过滤的规则。
9. RACStream
这个part可以参考:https://www.jianshu.com/p/0e4de0eeaff7 & https://www.jianshu.com/p/64ab974445dd
RACStream中的许多定义都是抽象的,没有具体实现,需要由其子类进行实现。
例如signal其实是继承自stream的:
@interface RACSignal<__covariant ValueType> : RACStream
- empty
+ (__kindof RACStream *)empty;
因为RAC中nil会导致crash,所以很多时候需要定义一个空对象来替代nil,一般empty都被创建为一个单例来使用。
- bind
适用于过滤
- (__kindof RACStream *)bind:(RACStreamBindBlock (^)(void))block;
懒绑定,将block返回的RACStream绑定到自身,在调用时才执行内部操作。很多操作都是基于bind的,比如flattenMap,大多时候我们都调用bind的上层方法。
例如,创建一个signal对象发送十次值,并且在其中随机发送@" "。我们要做的是过滤掉@" "并且在接收到@"5"时终止后续发送返回一个新的signal并且发送@"a",@"b":
RACSignal * signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//发送10次值
for (int i = 0; i < 10; i ++) {
//随机发送 @" "
BOOL empty = arc4random_uniform(2);
if (!empty || i == 5) {
[subscriber sendNext:[NSString stringWithFormat:@"%zd",i]];
}
else {
[subscriber sendNext:@" "];
}
}
[subscriber sendCompleted];
return nil;
}] bind:^RACStreamBindBlock{
return ^id (NSString * value, BOOL * stop) {
if ([value isEqualToString:@" "]) { //过滤@" "
return [RACSignal empty];
}
else if ([value isEqualToString:@"5"]){ //接收到 @"5" 终止
* stop = YES;
RACSignal * result = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"a"];
[subscriber sendNext:@"b"];
[subscriber sendCompleted];
return nil;
}];
return result;
}
else {
return [RACSignal return:value];
}
};
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[signal subscribeCompleted:^{
NSLog(@"complete");
}];
/* 某次执行结果
2018-03-27 15:14:02.334078+0800 ReactCocoaTest[91664:2403921] 1
2018-03-27 15:14:02.334390+0800 ReactCocoaTest[91664:2403921] 3
2018-03-27 15:14:02.334562+0800 ReactCocoaTest[91664:2403921] 4
2018-03-27 15:14:02.334836+0800 ReactCocoaTest[91664:2403921] a
2018-03-27 15:14:02.334957+0800 ReactCocoaTest[91664:2403921] b
2018-03-27 15:14:02.335673+0800 ReactCocoaTest[91664:2403921] complete
*/
- return
+ (__kindof RACStream *)return:(id)value;
把一个值包装成对应的RACStream的子类型。
- concat
适用于连续的异步任务
- (__kindof RACStream *)concat:(RACStream *)stream;
连接两个信号,子类实现具体如何连接。
例如酱紫:
RACSignal * signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"1"];
[[RACScheduler mainThreadScheduler]afterDelay:5 schedule:^{
[subscriber sendCompleted];
}];
return nil;
}];
RACSignal * signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"2"];
[[RACScheduler mainThreadScheduler]afterDelay:5 schedule:^{
[subscriber sendCompleted];
}];
return nil;
}];
RACSignal * concat = [signal1 concat:signal2];
[concat subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[concat subscribeCompleted:^{
NSLog(@"complete");
}];
[concat subscribeError:^(NSError *error) {
NSLog(@"error");
}];
/*
2018-03-27 16:37:18.546755+0800 ReactCocoaTest[94470:2467226] 1
2018-03-27 16:37:24.040041+0800 ReactCocoaTest[94470:2467226] 2
2018-03-27 16:37:29.513247+0800 ReactCocoaTest[94470:2467226] complete
*/
- zipWith
适用于连接两个信号的结果
- (__kindof RACStream *)zipWith:(RACStream *)stream;
压缩两个信号,子类实现具体如何压缩。
例如酱紫:
RACSignal * signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"1"];
[subscriber sendNext:@"2"];
[subscriber sendNext:@"3"];
[subscriber sendNext:@"4"];
[subscriber sendNext:@"5"];
[subscriber sendNext:@"6"];
[[RACScheduler mainThreadScheduler]afterDelay:7 schedule:^{
[subscriber sendError:nil];
}];
return nil;
}];
RACSignal * signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"a"];
[subscriber sendNext:@"b"];
[[RACScheduler mainThreadScheduler]afterDelay:5 schedule:^{
[subscriber sendNext:@"d"];
[subscriber sendCompleted];
}];
return nil;
}];
RACSignal * zip = [signal1 zipWith:signal2];
[zip subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[zip subscribeCompleted:^{
NSLog(@"complete");
}];
[zip subscribeError:^(NSError *error) {
NSLog(@"error");
}];
/*
2018-03-27 17:20:15.922457+0800 ReactCocoaTest[96661:2499724] <RACTuple: 0x604000206030> (
1,
a
)
2018-03-27 17:20:15.922787+0800 ReactCocoaTest[96661:2499724] <RACTuple: 0x604000206010> (
2,
b
)
2018-03-27 17:20:21.414965+0800 ReactCocoaTest[96661:2499724] <RACTuple: 0x604000207110> (
3,
d
)
2018-03-27 17:20:21.415268+0800 ReactCocoaTest[96661:2499724] complete
*/
10. RACTuple
RACTuple就和swift里面的元组一样的,随便放数据,它的内部主要就是一个可以放 id 类型的数组:
- (instancetype)initWithBackingArray:(NSArray *)backingArray {
self = [super init];
_backingArray = [backingArray copy];
return self;
}
使用就很简单,可以用init,也可以用提供的快捷方式:
@class RACTwoTuple<__covariant First, __covariant Second>;
@class RACThreeTuple<__covariant First, __covariant Second, __covariant Third>;
@class RACFourTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth>;
@class RACFiveTuple<__covariant First, __covariant Second, __covariant Third, __covariant Fourth, __covariant Fifth>;
11. RACSequence
它也是继承RACStream
的,所以也有各种zip、concat、bind的操作~
RAC对OC的集合和RACTuple进行Category扩充,因此可用集合.rac_sequence
,把集合快速转换成RACSequence对象
订阅RACSequence的signal,可遍历所有元素,但因为内部实现是异步执行的(for in是在当前线程),所以使用时候需要注意时间顺序。
NSArray *array = @[@1, @2, @3];
NSDictionary *dict = @{@"key1" : @"value1", @"key2" : @"value2", @"key3" : @"value3"};
NSString *str = @"ABC";
NSSet *set = [NSSet setWithArray:array];
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:array];
//NSArray 会返回元素
[array.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"array rac_sequence : %@", x);
}];
//NSDictionary 会返回打包成Tuple的key、value
[dict.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"dict rac_sequence : %@", x);
}];
//NSString 会返回单个字符
[str.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"str rac_sequence : %@", x);
}];
//NSSet 会返回元素
[set.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"set rac_sequence : %@", x);
}];
//RACTuple 会返回内置数组的元素
[tuple.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"tuple rac_sequence : %@", x);
}];
//以下是输出结果,从结果可以看出,结果是乱序的
//如果再打印当前线程,会发现每种集合都在各自的一条线程上输出,但非主线程
//array rac_sequence : 1
//array rac_sequence : 2
//array rac_sequence : 3
//str rac_sequence : A
//set rac_sequence : 3
//str rac_sequence : B
//tuple rac_sequence : 1
//set rac_sequence : 2
//dict rac_sequence : <RACTuple: 0x610000004cf0> (key1,value1)
//tuple rac_sequence : 2
//str rac_sequence : C
//set rac_sequence : 1
//dict rac_sequence : <RACTuple: 0x610000004ce0> (key3,value3)
//tuple rac_sequence : 3
//dict rac_sequence : <RACTuple: 0x608000004bb0> (key2,value2)
How?
RAC的信号的用法主要有什么呢?我们在MVVM或者MVC框架中都会有绑定V和 VM或者M 的操作,这个时候可以用signal来实现,比如view来订阅signal,然后VM在获取完数据以后可以sendNext把数据传给view即可。
为什么 RAC 是 响应式 & 函数式?
首先响应体现在 signal 上面,比如当我们点击了一个button,就会发给我们一个 signal ,我们可以在接受到 signal 以后做一些处理,这个就类似于我们设置在接受到 b 改变的 signal 里面设置a = b + c,那么每次 b 变化,a 都会自然的变化,不需要再手动改啦。
函数式比较隐密,因为OC里面函数是不能作为参数传递的,函数式只能体现在block上面,所以RAC里面通过block响应next就是函数式的体现了,和swift的promise里面的.then().next()之类的很类似。
References:
https://www.jianshu.com/p/5fc71f541b1c
https://www.jianshu.com/p/d90f6715f2cc
https://medium.com/@shaistha24/functional-programming-vs-object-oriented-programming-oop-which-is-better-82172e53a526
https://www.jianshu.com/p/9eee5a0b42da