一. MVVM
- 作用:维护性较强,耦合性低
- ViewModel: 相比较于MVC新引入的视图模型。是视图显示逻辑、验证逻辑、网络请求等代码存放的地方,唯一要注意的是,任何视图本身的引用都不应该放在VM中,换句话说就是VM中不要引入UIKit.h (对于image这个,也有人将其看做数据来处理,这就看个人想法了,并不影响整体的架构)。
MVVM详解
- MVVM模式主要目的是分离视图(view)和模型(model)
- 优点:
- 低耦合: 视图可以独立于model变化和修改,一个viewmodel可以绑定到不同的view,即view变化是model不变,model变化时view不变.
- 可重用性: 把一些视图逻辑放在一个viewmodel里面,让很多view重用这段视图逻辑
- 独立开发:
- 可测试: 界面素来是比较难预测是的,现在测试可以针对viewmodel来写
RAC
- 结合函数式响应式编程的框架,也可以称其为函数响应式编程框架,提供一个单一的,统一的方法去处理异步的行为,包括delegate方法,blocks回调,target-action机制,notifications和KVO.但是不要简单的只是单纯的认为他仅仅就是减少代码复杂度,更好的配合MVVM而已.
它最大的与众不同是提供了一种新的写代码的思维,由于RAC将Cocoa中KVO、UIKit event、delegate、selector等都增加了RAC支持,所以都不用去做很多跨函数的事。
如果全工程都使用RAC来实现,对于同一个业务逻辑终于可以在同一块代码里完成了,将UI事件,逻辑处理,文件或数据库操作,异步网络请求,UI结果显示,这一大套统统用函数式编程的思路嵌套起来,进入页面时搭建好这所有的关系,用户点击后妥妥的等着这一套联系一个个的按期望的逻辑和次序触发,最后显示给用户。
RAC详解
1.什么是RAC?
- RAC简单的说就是一个第三方库,它可以简化代码过程,提高开发效率,并且安全可靠。它具有函数式编程和响应式编程的特性.
2.使用RAC
- target-action
- RAC最基本的入门使用技巧就是监听事件.
- 代码示例
//文本框监听事件
[[self.textFild rac_signalForControlEvents:UIControlEventEditingChanged] subscribeNext:^(id x){
NSLog(@"change");
}]
//按钮监听事件
[self.button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x){
NSLog(@"点击事件");
}];
//添加点击事件
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];
[[tap rac_gestureSignal] subscribeNext:^(id x) {
NSLog(@"tap");
}];
[self.view addGestureRecognizer:tap];
- 代理
- 使用RAC写代理有局限性,因为它只能返回值为void的代理方法
- 代码示例
//已经弃用
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"RAC" message:@"RAC TEST" delegate:self cancelButtonTitle:@"cancel" otherButtonTitles:@"other", nil];
[[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple *tuple) {
NSLog(@"%@",tuple.first);
NSLog(@"%@",tuple.second);
NSLog(@"%@",tuple.third);
}];
[alertView show];
//简化代码
[[alertView rac_buttonClickedSignal] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
- 通知
- RAC中使用通知,并不需要移除,因为RAC本身提供了remove方法
- 代码示例
//发送通知
NSMutableArray *dataArray = [[NSMutableArray alloc] initWithObjects:@"1", @"2", @"3", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"postData" object:dataArray];
//接受通知-- 使用RAC
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"postData" object:nil] subscribeNext:^(NSNotification *notification) {
NSLog(@"%@", notification.name);
NSLog(@"%@", notification.object);
}];
- KVO
- RAC中KVO大部分都是宏定义的,简单来说就是
RACObserve(TARGET, KEYPATH)
这样的形式,TARGET是监听目标,KEYPATH是要观察的属性值. - 代码示例
UIScrollView *scrolView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 200, 400)];
scrolView.contentSize = CGSizeMake(200, 800);
scrolView.backgroundColor = [UIColor greenColor];
[self.view addSubview:scrolView];
[RACObserve(scrolView, contentOffset) subscribeNext:^(id x) {
NSLog(@"success");
}];
- RACSignal类
- 监听输入文本框文字变化
- 例如:监听textField文本变化(随着用户输入变化,且并不需要遵循协议以及添加任何事件)
[self.usernameTextField.rac_textSignal subscribeNext:^(id x){
NSLog(@"%@", x);
}];
- ReactiveCocoa signal(RACSignal)发送事件流给它的subscriber.且目前一共有三种类型事件:next error completed.一个signal在因error终止或者完成前可以发送任意数量的next事件
- RACSignal有很多方法可以来订阅不同的事件类型.每个方法都至少有一个block,当事件发生时就会执行block里面的逻辑
- 需求:判断用户名长度是否超过3个字符串长度可以使用filter来实现
- 代码:
[[self.usernameTextField.rac_textSignal
filter:^BOOL(id value){
NSString*text = value;
return text.length > 3;
}]
subscribeNext:^(id x){
//打印超过3之后的字符串
NSLog(@"%@", x);
}];
- filter操作的输出都是RACSignal
- RACSignal的每个操作都会返回RACSignal,也就是连贯接口(fluent interface),而这个功能可以让你直接构建管道,并不需要每一步都使用本地变量
- 代码:
RACSignal *usernameSourceSignal =
self.usernameTextField.rac_textSignal;
RACSignal *filteredUsername =[usernameSourceSignal
filter:^BOOL(id value){
NSString*text = value;
return text.length > 3;
}];
[filteredUsername subscribeNext:^(id x){
NSLog(@"%@", x);
}];
什么是事件?
- 事件可以包括任何事情
- 代码:
[[[self.usernameTextField.rac_textSignal
map:^id(NSString*text){
return @(text.length);
}]
filter:^BOOL(NSNumber*length){
return[length integerValue] > 3;
}]
subscribeNext:^(id x){
NSLog(@"%@", x);
}];
- 新加的map操作是通过block改变事件的数据,map从上一个next事件接收数据,通过执行block把返回值传给下一个next事件
创建有效状态信号
- 创建信号,判断输入用户名,密码是否有效.
- 将输入框设置为不同的背景颜色
- 代码:
RACSignal *validUsernameSignal =
[self.usernameTextField.rac_textSignal
map:^id(NSString *text) {
return @([self isValidUsername:text]);
}];
RACSignal *validPasswordSignal =
[self.passwordTextField.rac_textSignal
map:^id(NSString *text) {
return @([self isValidPassword:text]);
}];
RAC(self.passwordTextField, backgroundColor) =
[validPasswordSignal
map:^id(NSNumber *passwordValid){
return[passwordValid boolValue] ? [UIColor clearColor]:[UIColor yellowColor];
}];
RAC(self.usernameTextField, backgroundColor) =
[validUsernameSignal
map:^id(NSNumber *passwordValid){
return[passwordValid boolValue] ? [UIColor clearColor]:[UIColor yellowColor];
}];
- RAC宏允许直接把信号的输出应用到对象的属性上。RAC宏有两个参数,第一个是需要设置属性值的对象,第二个是属性名。每次信号产生一个next事件,传递过来的值都会应用到该属性上。
聚合信号
- 目前在应用中,登录按钮只有当用户名和密码输入框的输入都有效时才工作。现在要把这里改成响应式的。
- 代码:
RACSignal *signUpActiveSignal =
[RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]
reduce:^id(NSNumber*usernameValid, NSNumber *passwordValid){
return @([usernameValid boolValue]&&[passwordValid boolValue]);
}];
[signUpActiveSignal subscribeNext:^(NSNumber*signupActive){
self.signInButton.enabled =[signupActive boolValue];
}];
- ReactiveCocoa两个重要的概念
- 分割: 信号可以有很多subscribe,也就是作为很多后续步骤的源,
- 聚合: 多个信号可以聚合成一个新的信号,例如两个bool信号聚合成了一个,实际上你可以聚合并产生任何类型信号
- 这就是响应式编程的关键区别,不需要使用实例变量来追踪瞬间状态