·
·
文章目录
  1. 架构
  2. MVVM
  3. ReactiveCocoa
    1. RACSignal (信号)
    2. RACReplaySubject
    3. RACCommand
    4. 对于消息机制的封装
    5. 常见的方法
  4. 单例模式
  5. VIPER
    1. 交互器
    2. 实体
    3. 展示器
    4. 视图
    5. 路由
  6. 参考

objc中国学习--架构

从今天开始学习objc中国的所有博客。

架构

  • MVVM 介绍

  • 避免滥用单例

  • iOS 中的行为

  • 子类

  • 使用 VIPER 构建 iOS 应用

MVVM

mvvm是Model-View-ViewModel,将 Model 数据转换为 View 可以呈现的东西的事情。

通俗的讲,为胖的数值层,瘦的对象层

通过结合ReactiveCocoa便利性,这个模式提供了一个很好的代替MVC的方案,它保证了让视图控制器的轻量性。

View-Model 作为一个表现视图显示自身所需数据的静态模型;但它也有收集,解释和转换那些数据的责任。这留给了 view (controller) 一个更加清晰明确的任务:呈现由 view-model 提供的数据。

现在视图控制器仅关注于用 view-model 的数据配置和管理各种各样的视图,并在先关用户输入时让 view-model 获知并需要向上游修改数据. 视图控制器不需要了解关于网络服务调用,Core Data,模型对象等。

我们一般认为可以这样来处理:

1
2
3
4
5
                   - View
Foundation - UIKit - Controller
- Other - Network -
- Bussiness - View Model
- Persistence -

MVVM模式中的三部分比MVC更加简洁,下面是一些严格的限制

  1. View引用了ViewModel,但反过来不行。
  2. ViewModel引用了Model,但反过来不行。

如果我们破坏了这些规则,便无法正确地使用MVVM。

ReactiveCocoa

在编写iOS代码时,我们的大部分代码都是在响应一些事件:按钮点击、接收网络消息、属性变化等等。但是这些事件在代码中的表现形式却不一样:(我在某一张提到过)如target-action、Delegate、KVO、Block等。ReactiveCocoa的目的就是定义一个统一的事件处理接口,这样它们可以非常简单地进行链接、过滤和组合。ReactiveCocoa被当成是函数响应编程(Functional Reactive Programming,FRP)框架。

RACSignal (信号)

RACSignal (信号)就 RAC 来说是构造单元。 它代表我们最终将要收到的信息。当你能将未来某时刻收到的消息具体表示出来时, 你可以开始预先(陈述性)运用逻辑并构建你的信息流,而不是必须等到事件发生(命令式)。

信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。

默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。

如何订阅信号:调用信号RACSignal的subscribeNext就能订阅。

采用block的形式将信号发送一个事件流到它们的订阅者中。我们需要知道三种类型的事件:next,error和completed。一个信号可能由于error事件或completed事件而终止,在此之前它会发送很多个next事件。在这一部分中,我们将重点关注next事件。

ReactiveCocoa的核心是信号,它是一个事件流。使用ReactiveCocoa时,对于同一个问题,可能会有多种不同的方法来解决。ReactiveCocoa的目的就是为了简化我们的代码并更容易理解。

@weakify/@strongify 宏切换 strong 和 weak。这在创建所有这些 block 时非常重要。在 RAC 的 block 中使用 self 时将会被捕获为强引用并得到保留环, 除非你尤其意识到要破除保留环。

View 与 View Model 绑定

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
// RACSignal使用步骤:
// 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
// 2.发送信号 - (void)sendNext:(id)value
// 3.订阅信号,才会激活信号. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock

// RACSignal底层实现:
// 1.创建信号,首先把didSubscribe保存到信号中,还不会触发。
// 2.当信号被订阅,也就是调用signal的subscribeNext:nextBlock
// 2.2 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
// 2.1 subscribeNext内部会调用siganl的didSubscribe
// 3.siganl的didSubscribe中调用[subscriber sendNext:@1];
// 3.1 sendNext底层其实就是执行subscriber的nextBlock

// 1.创建信号
RACSignal *siganl = [RACSignal createSignal:^RACDisposable * (id<RACSubscriber> subscriber) {

// block调用时刻:每当有订阅者订阅信号,就会调用block。

// 2.发送信号
[subscriber sendNext:@1];

// 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
[subscriber sendCompleted];

return [RACDisposable disposableWithBlock:^{

// block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。

// 执行完Block后,当前信号就不在被订阅了。

NSLog(@"信号被销毁");

}];
}];

// 3.订阅信号,才会激活信号.
[siganl subscribeNext:^(id x) {
// block调用时刻:每当有信号发出数据,就会调用block.
NSLog(@"接收到数据:%@",x);
}];

RACReplaySubject

RACReplaySubject:重复提供信号者,RACSubject的子类。

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
54
55
56
57
// RACSubject使用步骤
// 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
// 2.订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 3.发送信号 sendNext:(id)value

// RACSubject:底层实现和RACSignal不一样。
// 1.调用subscribeNext订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock已经赋值了。
// 2.调用sendNext发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。

// 1.创建信号
RACSubject *subject = [RACSubject subject];

// 2.订阅信号
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第一个订阅者%@",x);
}];
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第二个订阅者%@",x);
}];

// 3.发送信号
[subject sendNext:@"1"];


// RACReplaySubject使用步骤:
// 1.创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。
// 2.可以先订阅信号,也可以先发送信号。
// 2.1 订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
// 2.2 发送信号 sendNext:(id)value

// RACReplaySubject:底层实现和RACSubject不一样。
// 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
// 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock

// 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
// 也就是先保存值,在订阅值。

// 1.创建信号
RACReplaySubject *replaySubject = [RACReplaySubject subject];

// 2.发送信号
[replaySubject sendNext:@1];
[replaySubject sendNext:@2];

// 3.订阅信号
[replaySubject subscribeNext:^(id x) {

NSLog(@"第一个订阅者接收到的数据%@",x);
}];

// 订阅信号
[replaySubject subscribeNext:^(id x) {

NSLog(@"第二个订阅者接收到的数据%@",x);
}];

RACCommand

RACCommand:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。

使用场景:监听按钮点击,网络请求

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// 一、RACCommand使用步骤:
// 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
// 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
// 3.执行命令 - (RACSignal *)execute:(id)input

// 二、RACCommand使用注意:
// 1.signalBlock必须要返回一个信号,不能传nil.
// 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
// 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
// 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。

// 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
// 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
// 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。

// 四、如何拿到RACCommand中返回信号发出的数据。
// 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
// 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。

// 五、监听当前命令是否正在执行executing

// 六、使用场景,监听按钮点击,网络请求


// 1.创建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal*(id input) {


NSLog(@"执行命令");

// 创建空信号,必须返回信号
// return [RACSignal empty];

// 2.创建信号,用来传递数据
return [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> subscriber) {

[subscriber sendNext:@"请求数据"];

// 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
[subscriber sendCompleted];

return nil;
}];

}];

// 强引用命令,不要被销毁,否则接收不到数据
_conmmand = command;


// 3.执行命令
[self.conmmand execute:@1];

// 4.订阅RACCommand中的信号
[command.executionSignals subscribeNext:^(id x) {

[x subscribeNext:^(id x) {

NSLog(@"%@",x);
}];

}];

// RAC高级用法
// switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
[command.executionSignals.switchToLatest subscribeNext:^(id x) {

NSLog(@"%@",x);
}];

// 5.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
[[command.executing skip:1] subscribeNext:^(id x) {

if ([x boolValue] == YES) {
// 正在执行
NSLog(@"正在执行");

}else{
// 执行完成
NSLog(@"执行完成");
}

}];

对于消息机制的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// KVO
[RACObserve(self, username) subscribeNext:^(id x) {
NSLog(@" 成员变量 username 被修改成了:%@", x);
}];

// target-action
self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal*(id input) {
NSLog(@" 按钮被点击 ");
return [RACSignal empty];
}];

// Notification
[[[NSNotificationCenter defaultCenter]
rac_addObserverForName:UIKeyboardDidChangeFrameNotification
object:nil]
subscribeNext:^(id x) {
NSLog(@" 键盘 Frame 改变 ");
}
];

// Delegate
[[self rac_signalForSelector:@selector(viewWillAppear:)] subscribeNext:^(id x) {
debugLog(@"viewWillAppear 方法被调用 %@", x);
}];

常见的方法

  1. 代替代理:
    rac_signalForSelector:用于替代代理。

  2. 代替KVO :
    rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。

  3. 监听事件:
    rac_signalForControlEvents:用于监听某个事件。

  4. 代替通知:
    rac_addObserverForName:用于监听某个通知。

  5. 监听文本框文字改变:
    rac_textSignal:只要文本框发出改变就会发出这个信号。

  6. 处理当界面有多次请求时,需要都获取到数据时,才能展示界面
    rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法。
    使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。

Demo

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
54
55
56
57
58
59
60
61
// 1.代替代理
// 需求:自定义redView,监听红色view中按钮点击
// 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
// rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
// 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
[[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
NSLog(@"点击红色按钮");
}];

// 2.KVO
// 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
// observer:可以传入nil
[[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {

NSLog(@"%@",x);

}];

// 3.监听事件
// 把按钮点击事件转换为信号,点击按钮,就会发送信号
[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {

NSLog(@"按钮被点击了");
}];

// 4.代替通知
// 把监听到的通知转换信号
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];

// 5.监听文本框的文字改变
[_textField.rac_textSignal subscribeNext:^(id x) {

NSLog(@"文字改变了%@",x);
}];

// 6.处理多个请求,都返回结果的时候,统一做处理.
RACSignal *request1 = [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> subscriber) {

// 发送请求1
[subscriber sendNext:@"发送请求1"];
return nil;
}];

RACSignal *request2 = [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
// 发送请求2
[subscriber sendNext:@"发送请求2"];
return nil;
}];

// 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
[self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];


}
// 更新UI
- (void)updateUIWithR1:(id)data r2:(id)data1
{
NSLog(@"更新UI%@ %@",data,data1);
}

单例模式

单例设计模式确切的说就是一个类只有一个实例,有一个全局的接口来访问这个实例。当第一次载入的时候,它通常使用延时加载的方法创建单一实例。

VIPER

VIPER 可以是视图 (View),交互器 (Interactor),展示器 (Presenter),实体 (Entity) 以及路由 (Routing) 的首字母缩写。简明架构将一个应用程序的逻辑结构划分为不同的责任层。这使得它更容易隔离依赖项 (如数据库),也更容易测试各层间的边界处的交互。

VIPER 的主要部分是:

1
2
3
4
5
6

视图:根据展示器的要求显示界面,并将用户输入反馈给展示器。
交互器:包含由用例指定的业务逻辑。
展示器:包含为显示(从交互器接受的内容)做的准备工作的相关视图逻辑,并对用户输入进行反馈(从交互器获取新数据)。
实体:包含交互器要使用的基本模型对象。
路由:包含用来描述屏幕显示和显示顺序的导航逻辑。

交互器

交互器在应用中代表着一个独立的用例。它具有业务逻辑以操纵模型对象(实体)执行特定的任务。交互器中的工作应当独立与任何用户界面。

实体

实体是被交互器操作的模型对象,并且它们只被交互器所操作。交互器永远不会传输实体至表现层 (比如说展示器)。

展示器

展示器是一个主要包含了驱动用户界面的逻辑的 PONSO,它总是知道何时呈现用户界面。基于其收集来自用户交互的输入功能,它可以在合适的时候更新用户界面并向交互器发送请求。

视图

视图一般是被动的,它通常等待展示器下发需要显示的内容,而不会向其索取数据。视图(例如登录界面的登录视图控件)所定义的方法应该允许展示器在高度抽象的层次与之交流。展示器通过内容进行表达,而不关心那些内容所显示的样子。展示器不知道 UILabel,UIButton 等的存在,它只知道其中包含的内容以及何时需要显示。内容如何被显示是由视图来进行控制的。

路由

屏幕间的路径会在交互设计师创建的线框 (wireframes) 里进行定义。在 VIPER 中,路由是由两个部分来负责的:展示器和线框。一个线框对象包括 UIWindow,UINavigationController,UIViewController 等部分,它负责创建视图/视图控制器并将其装配到窗口中。

参考

#13 架构

ReactiveCocoa指南一:信号

ReactiveCocoa指南二:Twitter搜索实例

ReactiveCocoa-and-MVVM-an-Introduction

MVVM指南一:Flickr搜索实例

ReactiveCocoa - iOS开发的新框架

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

iOS 设计模式系列:Singleton – 单例模式

最快让你上手ReactiveCocoa之基础篇

最快让你上手ReactiveCocoa之进阶篇

ReactiveCocoa学习笔记

说说ReactiveCocoa 2

这样好用的ReactiveCocoa,根本停不下来


版权声明



Ivan’s Blog by Ivan Ye is licensed under a Creative Commons BY-NC-ND 4.0 International License.
叶帆创作并维护的叶帆的博客博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证

本文首发于Ivan’s Blog | 叶帆的博客博客( http://yeziahehe.com ),版权所有,侵权必究。

本文链接:http://yeziahehe.com/2015/09/27/Objc_learning--architecture/
支持一下
扫一扫,支持yeziahehe
  • 微信扫一扫
  • 支付宝扫一扫