MVC:
Model: 负责存储,定义,操作数据; View: 用来展示给用户数据,和用户进行交互操作的; Controller:是 View 和 Model 的协调者,其可以直接与 Model 和 View 通信;而 View 不能和 Controller直接通信,需要利用代理协议的方式才能间接进行通信;或者通过 target-action 的方式进行通信, target 会留出 outlet 接口供 Controller 调用;同样 Model 和 Controller 也不能进行直接的通信,可以通过通知中心和 KVO 的方式来实现二者之间的通行; Model和 View 是不能直接进行通信的,只能通过 Controller 进行传递;
缺点:
- 增加系统和实现的复杂性,意味着将要管理更多的文件;
- 视图和控制器之间的联系过于紧密,耦合性太强;
- MVC 不适合小型甚至中等规模的应用程序;
- 视图和数据模型的低效率访问;
优点:
- 低耦合性;
- 可移植性好,可维护性强;
- 利于管理,较低的声明周期成本;
MVVM:
在MVC模式的iOS开发中,Controller承担了太多的代码,包含着我们的视图处理逻辑和业务逻辑;使 Controller 显得很臃肿, MVVM 就应运而生; 在MVVM中,我们将视图处理逻辑从C中剥离出来给V,剩下的业务逻辑部分被称做View-Model;
Model:数据层。
ViewModel层,就是View和Model层的粘合剂,他是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。说白了,就是把原来ViewController层的业务逻辑和页面逻辑等剥离出来放到ViewModel层。
View层,就是ViewController层,他的任务就是从ViewModel层获取数据,然后显示。
优点:
- MVVM 可以兼容你当下使用的 MVC 架构。
- MVVM 增加你的应用的可测试性。
- MVVM 配合一个绑定机制效果最好。 如我们之前所见,MVVM 基本上就是 MVC 的改进版,所以很容易就能看到它如何被整合到现有使用典型 MVC 架构的应用中;
当Model 是不可变的,我们可以只在初始化的时候指定我们 View Model 的属性。对于可变 Model,我们还需要使用一些绑定机制,这样 View Model 就能在背后的 Model 改变时更新自身的属性。此外,一旦 View Model 上的 Model 发生改变,那 View 的属性也需要更新。Model 的改变应该级联向下通过 View Model 进入 View。 在 OS X 上,我们可以使用 Cocoa 绑定,但在 iOS 上我们并没有这样好的配置可用。我们想到了 KVO(Key-Value Observation),而且它确实做了很伟大的工作。然而,对于一个简单的绑定都需要很大的样板代码,更不用说有许多属性需要绑定了。作为替代,我个人喜欢使用 ReactiveCocoa,但 MVVM 并未强制我们使用ReactiveCocoa。MVVM 是一个伟大的典范,它自身独立,只是在有一个良好的绑定框架时做得更好。
那 MVVM 相比 MVC 到底有哪些好处呢?主要可以归纳为以下三点: 1.由于展示逻辑被抽取到了 viewModel 中,所以 view 中的代码将会变得非常轻量级; 2.由于 viewModel 中的代码是与 UI 无关的,所以它具有良好的可测试性; 3.对于一个封装了大量业务逻辑的 model 来说,改变它可能会比较困难,并且存在一定的风险。在这种场景下,viewModel 可以作为 model 的适配器使用,从而避免对 model 进行较大的改动。
观察者模式(通知中心,KVO)
NSNotificationCenter
NSNotificationCenter是一种典型的有调度中心的观察者模式实现方式。以NSNotificationCenter为中心,观察者往Center中注册对某个主题对象的变化感兴趣,主题对象通过NSNotificationCenter进行变化广播。这种模型就是文章开始发布订阅报纸在OC中的一种类似实现。所有的观察和监听行为都向同一个中心注册,所有对象的变化也都通过同一个中心向外广播。 NSNotificationCenter就像一个枢纽一样,处在整个观察者模式的核心位置,调度着消息在观察者和监听者之间传递。
注意点:
- 观察者Observer,一般在需要接受通知的地方注册观察者,通过NSNotificationCenter的addObserver:selector:name:object接口来注册对某一类型通知感兴趣.在注册时候一定要注意,NSNotificationCenter不会对观察者进行引用计数+1的操作,我们在程序中释放观察者的时候,一定要去报从center中将其注销了。
- 主题对象,被观察的对象,通过postNotificationName:object:userInfo:发送某一类型通知,广播改变。
- 通知对象NSNotification,当有通知来的时候,Center会调用观察者注册的接口来广播通知,同时传递存储着更改内容的NSNotification对象
KVO(Key-Value Observation)
即键值观察。是一种没有中心枢纽的观察者模式的实现方式。一个主题对象管理所有依赖于它的观察者对象,并且在自身状态发生改变的时候主动通知观察者对象。 完成一次完整的改变通知过程,经过以下几次过程: 1.注册观察者[message addObserver:self forKeyPath:KVOPathKey options:NSKeyValueObservingOptionNew context:Nil]; 2.更改主题对象属性的值,即触发发送更改的通知 _message.key = @”message”; 3.在制定的回调函数中,处理收到的更改通知; 4.注销观察者 [_message removeObserver:self forKeyPath:kKVOPathKey];
KVO 的底层实现原理
KVO 是基于 RunTime 机制实现的,当某一个类的实例第一次使用KVO的时候,系统就会在运行期间动态的创建该类的一个派生类,该类的命名规则一般是以NSKVONotifying为前缀,以原本的类名为后缀。并且将原型的对象的isa指针指向该派生类。同时在派生类中重载了使用KVO的属性的setter方法,在重载的setter方法中实现真正的通知机制。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。因此,在使用的时候 KVO 的性能不如通知中心;
KVO 的缺陷和解决办法:
缺陷处理结果分析:
- 所有的observe处理都放在一个方法里。解决。因为回调被分散成块了,不再集中。但是用块也不是很统一,但个人觉得还是比原生的好点。
- 严重依赖于string。未解决。
- 需要自己处理superclass的observe事务。解决。所有执行了xw_addObserverBlockForKeyPath的块都会被放入KVOBlockSet,对应的keyPath值被改变时会回调对应的所有block,不存在调用super的问题。
- 多次相同KVO的removeObserve会导致crash。作者用的hook dealloc调剂remove方法达到自动移除功能,目前来看逻辑没问题。
代理(委托)设计模式
什么是代理? 这是苹果官方文档的解释: Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other object—the delegate—and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object. 翻译一下大概是: 代理是一种简单而功能强大的设计模式,这种模式用于一个对象“代表”另外一个对象和程序中其他的对象进行交互。 主对象(这里指的是delegating object)中维护一个代理(delegate)的引用并且在合适的时候向这个代理发送消息。这个消息通知“代理”主对象即将处理或是已经处理完了某一个事件。这个代理可以通过更新自己或是其它对象的UI界面或是其它状态来响应主对象所发送过来的这个事件的消息。或是在某些情况下能返回一个值来影响其它即将发生的事件该如何来处理。代理的主要价值是它可以让你容易的定制各种对象的行为。注意这里的代理是个名词,它本身是一个对象,这个对象是专门代表被代理对象来和程序中其他对象打交道的。 简单来说就是: 代理的目的是改变或传递控制链。允许一个类在某些特定时刻通知到其他类,而不需要获取到那些类的指针。可以解除对象之间的耦合性,属于一种回调机制,是一对一的关系。
代理设计模式的使用场景:
1,A 想让B 帮他做些事情,可以让B成为A的代理先 2,A 想通知B,A发生了一些事情,可以让B成为A的代理先 3,B 想监听A发生了一些事情,可以让B成为A的代理先
代理设置模式的标准4步
1,定义一份protocol协议 2,B想做代理,必须先遵守并实现上面那份协议 3,A里面定义一个成员 id delegate 4,将B的实例对象,赋值给A的成员变量delegate
总结KVO、代理和通知
KVO的正确使用方法:
1.使用静态变量的地址作为context,并且为每一个监听的属性都创建一个context,尽量不使用keypath作为区分条件;
2.addObserver和removeObserver必须成对出现,建议在dealloc方法中删除监听器对象;
3.如果有继承关系,在监听器回调函数中将不是当前类处理的context调用父类的监听器回调函数进行处理;
4.删除监听器时需要注意不要重复删除,尽量使用context删除;
5.手动调用KVO或者满足某个条件才调用,就是将方法+ (BOOL)automaticallyNotifiesObserversOfXXX
;重写返回NO;然后在需要调用KVO的时候调用
[self willChangeValueForKey:@“xxxx”];
//为其赋新值 _balance = balance;
[self didChangeValueForKey:@“xxxx"];
KVO、代理和通知的优缺点: KVO是一个对象能观察另一个对象属性的值,代理、通知两种模式更适合一个controller和其它的对象进行通信,而KVO适合任何对象监听另一个对象的改变,这是一个对象与另外一个对象保持同步的一种方法。KVO只能对属性改变做出反应,不会用来对方法或者动作做出反应; 优点:
- 创建监听器的实现简单,只需要注册后实现回调函数即可
- 提供一个简单的方法来实现两个对象的同步,并且能对非我们创建的对象作出反应;
- 能够实现多对一的监听,多个对象可同时监听同一个对象属性值的变化
- KVO提供了监听新值以及旧值的方法,可以获取到修改前的值
- 支持keyPath来监听嵌套属性值,context区分监听器 缺点:
- 注册监听器和删除监听器必须成套出现
- 重复删除监听器会发生异常
- 监听器对象销毁前未删除监听器可能发生野指针异常
- 继承类的KVO处理较复杂
- keyPath为字符串类型不能提供编译器检查
- 监听的属性值源码的名称发生变化需要修改代码 代理 优点
- 基于协议实现,提供了规范化的实现方法
- 在编译期就能够检查是否实现了代理必须实现的方法
- 提供事件响应的代理模式
- 提供数据源的代理模式
- 即使没有委托对象也不会产生异常
缺点:
- 规范化带来了实现上的复杂,必须遵守协议并实现所有方法
- 只能实现一对一的通信,如果多个对象都委托同一代理,为了区分不同的被委托对象,造成代码的复杂化 通知 通知中心的实现原理:
- NSNotificatinonCenter是使用观察者模式来实现用于跨层传递消息的,用来降低耦合度;
- NSNotificatinonCenter用来管理通知,将观察者注册到NSNotificatinonCenter的通知调度表中,然后发送通知时利用标识符name和objects识别出调度表中的观察者,然后调用相应的观察者方法,即OC中的传递消息; 优点:
- 创建通知的监听器简单,只需注册后实现监听放法即可
- 能够实现多对一的监听
- 通过NSNotification的userInfo能够传递通知的信息
- iOS9以后不需要手动删除监听器对象也不会产生异常
缺点:
- 通知名称使用字符串类型,在编译器无法检查
- 参数传递使用userInfo字典类型,参数获取需要规范定义
- 不能获取发送通知对象的状态信息
- 调试的时候,程序的工作以及控制流程难以追踪
单例设计模式
顾名思义,单例,即是在整个项目中,这个类的对象只能被初始化一次。它的这种特性,可以广泛应用于某些需要全局共享的资源中,比如管理类,引擎类,也可以通过单例来实现传值。如UIApplication;
单例的创建方式: 1、懒汉式:
static id _instance;
+(id)allocWithZone:(struct _NSZone *)zone
// alloc方法内部会调用这个方法
{
if (_instance == nil) { // 防止频繁加锁
@synchronized(self) {
if (_instance == nil) { // 防止创建多次
_instance = [super allocWithZone:zone];
}
}
}
return _instance;
}
+(instancetype)sharedMusicTool
{
if (_instance == nil) { // 防止频繁加锁
@synchronized(self) {
if (_instance == nil) { // 防止创建多次
_instance = [[self alloc] init];
}
}
}
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
2、饿汉式
static id _instance;
/**
* 当类加载到OC运行时环境中(内存),就会调用一次(一个类只会加载1次)
*/
+ (void)load
{
_instance = [[self alloc] init];
}
+ (id)allocWithZone:(struct _NSZone *)zone
{
if (_instance == nil) { // 防止创建多次
_instance = [super allocWithZone:zone];
}
return _instance;
}
+ (instancetype)sharedSoundTool
{
return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
3、GCD
// 用来保存唯一的单例对象
static id _instace;
+ (id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [super allocWithZone:zone];
});
return _instace;
}
+ (instancetype)sharedDataTool
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [[self alloc] init];
});
return _instace;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instace;
}