iOS高级函数响应式的框架之ReactiveCocoa

你真的会用block吗?

1
2
3
block作为对象的`属性`
block作为方法的`参数`
block作为`返回值` (扩展性特别强)

在强化一下:

#1.block作为对象的属性

1
2
3
4
5
6
7
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
/* block 在arc下用strong就可以 非ARC下 copy */
/** block */
@property (nonatomic,copy) void(^block)();
@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
/** Person *p */
@property (nonatomic,strong) Person *p;
@end
- (void)viewDidLoad {
[super viewDidLoad];

Person *p = [[Person alloc] init];
void(^XDBlock)() = ^() {

NSLog(@"XDBlock");
};
//调用
// XDBlock();
p.block = XDBlock;
_p = p;

}
$NSLog:XDBlock

#2.block 作为方法的参数

1
2
3
4
5
6
7
8
Person.h
#import <Foundation/Foundation.h>

@interface Person : NSObject

- (void)eat:(void(^)(NSString *))block;

@end

1
2
3
4
5
6
Person.m
@implementation Person
- (void)eat:(void (^)(NSString *))block{

block(@"😄");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
/** Person *p */
@property (nonatomic,strong) Person *p;
@end
- (void)viewDidLoad {
[super viewDidLoad];

Person *p = [[Person alloc] init];
[p eat:^(NSString *a) {

//这里面的代码块就是传递的参数
NSLog(@"吃东西%@",a);
}];


}
$NSLog:吃东西😄

#3.block作为返回值 (扩展性特别强)

1
2
3
4
5
6
7
8
Person.h
#import <Foundation/Foundation.h>

@interface Person : NSObject

- (void(^)(int))run;

@end

1
2
3
4
5
6
7
Person.m
@implementation Person
- (void(^)(int))run{
return ^(int m){
NSLog(@"跑了%d米",m);
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
/** Person *p */
@property (nonatomic,strong) Person *p;
@end
- (void)viewDidLoad {
[super viewDidLoad];

Person *p = [[Person alloc] init];
Person *p = [[Person alloc] init];
// [p run:3];
p.run(3);


}
$NSLog:2017-06-13 15:08:32.164 Block[30445:3119801] 跑了3米

首先得知道RAC是什么:
ReactiveCocoa-GitHub

1
2
3
4
RAC 是什么?

RAC - `ReactiveCocoa` 是一个函数响应式的框架
GitHub上的开源框架 在5.0以后的版本就是swift的了

集成ReactiveCocoa框架到项目 项目增大2-2.4M
注意纯OC项目目前集成ReactiveObjC (3.0.0)
纯swift项目集成ReactiveCocoa (5.0.3)
至于为什么,就不再做解释了。

1
2
3
4
5
现在以OC项目集成为例
1、创建项目`RAC-demo`
2、终端 cd 到项目 再执行命令
`$ vi Podfile`
3、把下边的复制到Podfile
1
2
3
4
5
6
use_frameworks!
platform :ios, "9.0"

target 'RAC-demo' do
pod 'ReactiveObjC', '~> 3.0.0'
end
1
2
3
4
4、执行命令:
$:pod install
5、关闭项目,再次打开xcworkspace后缀的文件编译一下没问题。
OK 下边可以写代码了:

6、引入框架的头文件

1
#import <ReactiveObjC/ReactiveObjC.h>

RACSignal信号类 是RAC里面最常见最常用的类

1
2
3
1、通过这个类创建一个信号RACSignal(默认是冷信号);
2、通过订阅者订阅信号(这个信号变为热信号);
3、发送信号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 //1、创建一个信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {

//3、发送信号
//subscriber 发送

[subscriber sendNext:@"发送信号"];

return nil;

}];
//2、订阅信号
//nextBlock这个block 调用:只要订阅者发送数据就调用
//nextBlock 作用:处理数据的

[signal subscribeNext:^(id _Nullable x) {

//x就是信号发送的内容

NSLog(@"订阅的信号是: %@",x);

}];
//2017-06-13 16:50:11.399 RAC-demo[32406:3606231] 订阅的信号是发送信号

实现的原理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1.创建RACDynamicSignal信号:

[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {}]

'cmd 点进去'

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
return [RACDynamicSignal createSignal:didSubscribe];
}
createSignal:点进去:可以发现
@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber);

@end

@implementation RACDynamicSignal

#pragma mark Lifecycle

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
'将 'didSubscribe' 被 '_didSubscribe'
signal->_didSubscribe = [didSubscribe copy];
保存起来'

保存Block《didSubscribe》
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2.订阅信号

创建订阅者
RACSubscriber

'subscribeNext:^(id _Nullable x)
cmd 点进去'

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);

RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}
'subscriberWithNext:nextBlock error:NULL completed:NULL'
'cmd 点进去'
+ (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;
}
'将 'next' 被 '_next'
subscriber->_next = [next copy];'
'保存起来'
保存Block《nextBlock》
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2.1 真正订阅信号
'cmd 点进去 [self subscribe:o]'
self ==> RACDynamicSignal

- (RACDisposable *)subscribe:(id<RACSubscriber>)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;
}
【实现了第一次调用:执行 didSubscribe】
'self.didSubscribe(subscriber);'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
3、发送信号、数据
//subscriber ==RACSubscriber 发送

[subscriber sendNext:@"发送信号"];

'cmd 点进去 sendNext'
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;

nextBlock(value);
}
}
【实现了第二次调用:执行 nextBlock】
'nextBlock(value);'

//x就是信号发送的内容

NSLog(@"订阅的信号是: %@",x);
//Log:2017-06-13 16:50:11.399 RAC-demo[32406:3606231] 订阅的信号是发送信号

流程图示:

实现原理图示

RAC的实现原理2-RACDisposable

RACDisposable

1
2
3
4
5
RACDisposable:它可以帮助我们取消订阅.

比如:信号发送完毕了 、或者信号发送失败了的时候都需要手动去取消订阅。

RACSubscriber(协议):订阅者(发送信号!)

看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#import "ViewController.h"
#import <ReactiveObjC/ReactiveObjC.h>

@interface ViewController ()
/** id<RACSubscriber> */
@property (nonatomic,strong) id<RACSubscriber> subscriber;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//1、创建一个信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {

//3、发送信号
//subscriber 发送
[subscriber sendNext:@"发送信号"];

_subscriber = subscriber;//可以用self.,强引用一下subscriber就不会走下边了,不强引用着subscriber 的话subscriber在发送完之后就没了就 自动取消订阅了

//返回值的类型RACDisposable
//RACDisposable可以帮助我们取消订阅:信号发送完毕或者失败了。(就像通知的注册和注销)
return [RACDisposable disposableWithBlock:^{

//清空资源

NSLog(@"到这了");

}];

}];

//2、订阅信号

RACDisposable *disposable = [signal subscribeNext:^(id _Nullable x) {

//x就是信号发送的内容
NSLog(@"订阅的信号是: %@",x);

}];
[disposable dispose];//手动 取消订阅


//信号发送完毕了 默认就会取消订阅
//只要订阅者在就不会主动取消订阅 如上边强引用着 subscriber 此时可以手动取消订阅





}

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
subscriber 在信号发送完毕的时候会主动消失,
此时如果你再强引用一下它的话,
subscriber就不会消失,相当于信号没有发送完,就不会主动取消订阅了。

[signal subscribeNext:^(id _Nullable x) 这个方法放入返回值`RACDisposable`类型的 这个类的对象可以帮助我们实现手动取消订阅:

RACDisposable *disposable = [signal subscribeNext:^(id _Nullable x) {

//x就是信号发送的内容
NSLog(@"订阅的信号是: %@",x);

}];
[disposable dispose];//手动 取消订阅

RAC的实现原理3-RACSubject

RACSubject 信号提供者!!,自己可以充当信号,又能够发送信号!!

#首先回顾一下

1
2
3
4
5
RACDisposable:它可以帮助我们取消订阅.信号发送完毕了 ,失败了.

RACSubscriber(协议):订阅者(发送信号!)

RACSubject :信号提供者!!,自己可以充当信号,又能够发送信号!!

##RACSubject:这个类叫做信号提供者,自己可以充当信号,又能够发送信号!!

1
@interface RACSubject<ValueType> : RACSignal<ValueType> <RACSubscriber>

1
2
3
4
5
6
编程思想:《面向协议的开发》

OC里边没有多继承这一说,那么我(RACSubject)想继承另一个类(RACSignal)里的功能:
就需要 我(RACSubject) 遵守 订阅者协议< RACSubscriber >,实现订阅者协议的方法,就可以了。

就是面向协议的开发的应用场景。

还是三步走:

1
2
3
1.创建信号
2.订阅信号
3.发送数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//1.创建信号
RACSubject *subject = [RACSubject subject];

//2.订阅信号
//不同的信号订阅的方式不一样!!因为类型不一样,所以调用的方法不一样。
//RACSubject处理订阅 :拿到之前的_subscribers 保存订阅者
[subject subscribeNext:^(id _Nullable x) {

NSLog(@"接收到的数据 x 是 %@",x);

}];



//3.发送数据
//遍历出所有的订阅者,其实还是调用的nextBlock
[subject sendNext:@"数据A"];

打印的结果:

######2017-06-15 16:18:54.468 RAC-demo[41104:6465743] 接收到的数据 x 是 数据A

#多订阅者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//1.创建信号
RACSubject *subject = [RACSubject subject];

//2.订阅信号
//不同的信号订阅的方式不一样!!因为类型不一样,所以调用的方法不一样。
//RACSubject处理订阅 :拿到之前的_subscribers 保存订阅者
[subject subscribeNext:^(id _Nullable x) {

NSLog(@"订阅1⃣️ 接收到的数据 x 是 %@",x);

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

NSLog(@"订阅2⃣️ 接收到的数据 x 是 %@",x);

}];


//3.发送数据
//遍历出所有的订阅者,其实还是调用的nextBlock
[subject sendNext:@"数据A"];

打印的结果:

######2017-06-15 17:04:56.804 RAC-demo[41379:6621767] 订阅1⃣️ 接收到的数据 x 是 数据A

######2017-06-15 17:04:56.805 RAC-demo[41379:6621767] 订阅2⃣️ 接收到的数据 x 是 数据A

#实现原理

1、创建信号
订阅管理者(_disposable)、保存订阅者的数组(_subscribers)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
" cmd "点"subject"进去
" [RACSubject subject] "

// This should only be used while synchronized on `self`.
@property (nonatomic, strong, readonly) NSMutableArray *subscribers;

// Contains all of the receiver's subscriptions to other signals.
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;

+ (instancetype)subject {
return [[self alloc] init];
}

- (instancetype)init {
self = [super init];
if (self == nil) return nil;

_disposable = [RACCompoundDisposable compoundDisposable];
_subscribers = [[NSMutableArray alloc] initWithCapacity:1];

return self;
}

- (void)dealloc {
[self.disposable dispose];
}
作者在重写的init方法里面进行了创建信号订阅管理者(_disposable)、保存订阅者的数组(_subscribers),
便于多个订阅者订阅
"_disposable"、"_subscribers"

2、订阅信号
RACSubject处理订阅 :拿到之前的_subscribers 保存订阅者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
"cmd"点"subscribeNext "进去
" [subject subscribeNext:^(id _Nullable x) {}] "

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
NSCParameterAssert(nextBlock != NULL);

RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
return [self subscribe:o];
}

保存Block
nextBlock

注意" [self subscribe:o] "
"cmd"点"subscribe "进去
此处的"self"代表的是"RACSubject"

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);

RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

NSMutableArray *subscribers = self.subscribers;
@synchronized (subscribers) {
[subscribers addObject:subscriber];
}

[disposable addDisposable:[RACDisposable disposableWithBlock:^{
@synchronized (subscribers) {
// Since newer subscribers are generally shorter-lived, search
// starting from the end of the list.
NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
return obj == subscriber;
}];

if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
}
}]];

return disposable;
}

保存所有订阅者
[subscribers addObject:subscriber];

#@synchronized上锁的原因是:@synchronized() 的作用是:
#创建一个互斥锁,保证在同一时间内没有其它线程对self对象进行修改,起到线程的保护作用,
#一般在公用变量的时候使用,如单例模式或者操作类的static变量中使用。

这里把订阅管理者disposable返回出去便于 手动 取消订阅

3、发送数据
遍历出所有的订阅者,其实还是调用的nextBlock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"cmd"点"sendNext"进去
"[subject sendNext:@"数据A"]"

- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
}];
}
看到"enumerateSubscribersUsingBlock"就知道这是个循环,
就是要把之前保存的订阅者一个一个找出发送信号
#下边这个方法证实了这个的想法:
- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
NSArray *subscribers;
@synchronized (self.subscribers) {
subscribers = [self.subscribers copy];
}

for (id<RACSubscriber> subscriber in subscribers) {
block(subscriber);
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#block(subscriber);
block一调用就近到了"[subscriber sendNext:value];"方法
#pragma mark RACSubscriber

- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;

nextBlock(value);
}
}
#看到了熟悉的
nextBlock(value);
这就表示在这里进行了发送数据把保存的nextBlock一执行就实现了数据返回后的处理了。

流程图示:
实现原理图示.png

iOS RAC的应用场景之一

在storeboard上边拖个View和一个按钮

在storeboard上边拖个View和一个按钮

关联一下

关联一下

不好拖到XDView的话先手敲代码,反拖过去:

1
- (IBAction)btnClick:(id)sender


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
XDView.h
#import <UIKit/UIKit.h>
#import <ReactiveObjC/ReactiveObjC.h>
@interface XDView : UIView
/** RACSubject */
@property (nonatomic,strong) RACSubject *btnClickSignal;

@end
XDView.m
#import "XDView.h"

@implementation XDView

-(RACSubject *)btnClickSignal{

if (_btnClickSignal == nil) {
_btnClickSignal = [RACSubject subject];
}
return _btnClickSignal;
}
- (IBAction)btnClick:(id)sender{


[self.btnClickSignal sendNext:@"按钮点击了,数据来了"];


}

@end
#import "ViewController.h"
#import <ReactiveObjC/ReactiveObjC.h>
#import "XDView.h"
@interface ViewController ()
@property (strong, nonatomic) IBOutlet XDView *XD_View;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//订阅信号
[self.XD_View.btnClickSignal subscribeNext:^(id _Nullable x) {
//想做什么
NSLog(@"x === %@",x);

}];

}

点击按钮之后打印结果:

2017-06-15 18:10:51.386 RAC-demo[42160:6990781] x === 按钮点击了,数据来了

#无所不能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (IBAction)btnClick:(id)sender{


[self.btnClickSignal sendNext:self.backgroundColor];

}

- (void)viewDidLoad {
[super viewDidLoad];

//订阅信号
[self.XD_View.btnClickSignal subscribeNext:^(id _Nullable x) {
//想做什么
NSLog(@"x === %@",x);
self.view.backgroundColor = x;
}];




}

###点击按钮之后:

效果

id 什么都可以代替想怎么玩怎么玩

1
2
3
4
5
- (void)sendNext:(id)value {
[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
[subscriber sendNext:value];
}];
}

##value可以是任何:值,方法,代码块等等,例如放个:网络请求。
欢迎骚扰:QQ:1434619565