单例\通知\代理
- 单例模式
- 什么是单例模式?
- 单例模式的优缺点
- 优点:
- 缺点:
- 实现方式
- 懒汉式:
- 饿汉式:
- 通知
- 代理
- 总结
- KVO\KVC\单例模式\通知\代理\Block
单例模式
什么是单例模式?
单例模式在整个工程中,相当于一个全局变量,就是不论在哪里需要用到这个类的实例变量,都可以通过单例方法来取得,而且一旦你创建了一个单例类,不论你在多少个洁面中初始化调用了这个单例方法取得对象,它们所有的对象都是指向的同一块内存的存储空间(即单例类保证了该类的实例对象是唯一存在的一个)
单例模式的优缺点
优点:
- 单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
- 单例因为类控制了实例化的过程,所以类可以更加灵活的修改实例化的过程
缺点:
- 单例对象一旦建立,对象指针式保存在静态区,单例对象在堆中分配的内存空间会在应用程序中止后才会被释放(和静态变量相似,只要进程在,单例对象就在)。
- 单例类无法继承,因此很难进行类的扩展。
- 单例不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就回引起数据的错误,不能保存彼此的状态。
实现方式
懒汉式:
实现原理和懒加载很想,如果在程序中不适用这个对象,那么就不会创建,只有在你使用代码创建这个对象,才会创建。这种思想在iOS开发中是很重要的,也是最常见的时间换空间的做法 (其实就是在第一次使用单例的时候才进行初始化)
我们需要重写alloc
方法,这里提供了两种方法,一种是alloc
,一种是allocWithZone
方法
其实在alloc
调用的底层也是allocWithZone
方法,所以在此,我们只需要重写allocWithZone
方法
id manager;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
// 在这里判断,为了优化资源,防止多次加锁和判断锁
if (manager == nil) {
// 在这里加一把锁(利用本类为锁)进行多线程问题的解决
@synchronized(self){
if (manager == nil) {
// 调用super的allocWithZone方法来分配内存空间
manager = [super allocWithZone:nil];//处于历史原因,OC不再使用NSZone,选择直接忽略
}
}
}
return manager;
}
但是这样的话还不够优化,我们还可以使用GCD方法进行一个优化:
首先我们来看一个GCD的API:
dispatch_once
一次性代码(只会执行一次):
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//此处编写只执行一次的代码(这里main默认是线程安全的)
});
同时对manager
应该使用static
以避免被extern
进行了操作
这里我们浅讲一下static
和extern
的区别:
static
:定义在变量或函数前面,它的含义是改变默认的external
链接属性,使它们的作用域限定在本文件内部,这样其他类的文件就不能对它做引用和修改了,保证了单例的一个安全性。extern
:修饰符extern
用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用,并可以对该变量/函数进行修改”
使用GCD的单例模式,添加了一个初始化单例的类方法来作为外部创建单例对象的接口:
static id manager;
//自定义创建单例的类方法接口
+ (instancetype)sharedManger{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [super allocWithZone:zone];
});
return manager;
}
//但是这时候我们还是得确保安全去重写一下allocWithZone方法,否则外部如果采用alloc的方法来创建单例对象的时候就会每alloc一遍就会新创建一个该对象,单例就失去了意义
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
if (manager == nil) {
manager = [super allocWithZone:zone];
}
return manager;
}
饿汉式:
在使用代码去创建对象之前就已经创建好了对象。
load
方法:当类加载到运行环境中的时候就会调用且仅调用一次,同时注意一个类只会加载一次(类加载有别于引用类,可以这么说,所有类都会在程序启动的时候加载一次,不管有没有在目前显示的视图类中引用到,这个涉及到了App的启动流程)initialize
方法:当第一次使用类的时候加载且仅加载一次
我们再来考虑:
- 在不考虑开发者主动使用的情况下,系统最多会调用一次
- 如果父类和子类都被调用,父类的调用一定在子类之前
- 都是为了应用运行前创建合适的运行环境
在使用时都不要过重地依赖于这两个方法,除非真正必要
它们的相同点在于:方法只会被调用一次。
二者相比较:
load
是只要类所在文件被引用就会被调用,而 initialize
是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load
调用;但即使类文件被引用进来,但是没有使用,那么 initialize
也不会被调用。
它们的相同点在于:方法只会被调用一次。
所以选择load
static id manager;
+ (void)load {
//只会加载一次也就不需要加锁
manager = [[self alloc] init];
}
+ (instancetype)sharedManger{
if (manager == nil) {
manager = [super allocWithZone:zone];
}
return. manager;
}
//但是这时候我们还是得确保安全去重写一下allocWithZone方法,否则外部如果采用alloc的方法来创建单例对象的时候就会每alloc一遍就会新创建一个该对象,单例就失去了意义
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
if (manager == nil) {
manager = [super allocWithZone:zone];
}
return manager;
}
通知
最熟悉的观察者模式
- 观察者和被观察者都无需知晓对方,只需要通过标记在
NSNotificationCenter
中找到监听该通知所对应的类,从而调用该类的方法。 - 并且在
NSNotificationCenter
中,观察者可以只订阅某一特定的通知,并对其做出相应,而不用对某一个类发的所有通知都进行更新操作。 NSNotificationCenter
对观察者的调用不是随机的,而是遵循注册顺序一一执行的,并且在该线程内是同步的。
通知的具体使用步骤:
- 创建通知对象
NSNotification *notice = [NSNotification notificationWithName:@"send" object:self userInfo:@{@"name":_renameTextField.text,@"pass":_repassTextField.text}];
- 通知中心发送通知
[[NSNotificationCenter defaultCenter] postNotification:notice];
- 注册通知 添加观察者来指定一个方法、名称和对象,接受到通知时执行这个指定的方法。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(recive:) name:@"send" object:nil];
- 接受通知后调用的方法
- (void)recive:(NSNotification *)notice {
NSDictionary *dictionary = notice.userInfo;
_nameTextField.text = dictionary[@"name"];
_passTextField.text = dictionary[@"pass"];
}
总结一下通知的用法:
- 接收通知的类注册监听者并实现接收通知的事件函数
- 触发通知的类在适当的时候发送通知
代理
又称委托代理,是iOS中常用的一种设计模式
协议,是多个类共享的一个方法列表,在协议中所列出的方法没有响应的实现,由其它类来实现。
委托是指给一个对象提供机会对另一对象中的变化做出反应或者相应另一个对象的行为。其基本思想是协同解决问题。
从方法的定义我们不难看出委托模式能够起到两方面的作用:
第一:代理协助对象主体完成某项操作,将需要定制化的操作通过代理来自定义实现,达到和子类化对象主体同样的作用。
第二:事件监听,代理对象监听对象主体的某些重要事件,对事件做出具体响应或广播事件交给需要作出响应的对象。
关于协议传值和属性传值
总结
KVO\KVC\单例模式\通知\代理\Block
KVO/通知 -------> 观察者模式
KVC --------> KVC模式
单例模式
代理模式
1. 代理和通知的区别
效率:代理比通知高;
关联:delegate
是强关联,委托和代理双方互相知道。通知是弱关联,不需要知道是谁发,也不需要知道是谁接收。
代理是一对一的关系,通知是一对多的关系。delegate
一般是行为需要别人来完成。通知是全局通知。
代理要实现对多个类发出消息可以通过将代理者添加入集合类后遍历,或通过消息转发来实现。
2. KVO和通知的区别
相同:都是一对多的关系;
不同:通知是需要被观察者先主动发出通知,观察者注册监听再响应,比KVO
多了发送通知这一步。
监听范围:KVO
是监听一个值的变化。通知不局限于监听属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用更灵活。
使用场景:KVO
的一般使用场景是监听数据变化,通知是全局通知。
3. block和代理的区别
相同点:block
和代理
都是回调的方式。使用场景相同。
不同点:
block
集中代码块,而代理分散代码块。所以block
更适用于轻便、简单的回调,如网络传输。 代理适用于公共接口较多的情况,这样做也更易于解耦代码架构。block
运行成本高。block
出栈时,需要将使用的数据从栈内存拷贝到堆内存。当然如果是对象就是加计数,使用完或block
置为nil
后才消除。 代理只是保存了一个对象指针,直接回调,并没有额外消耗。相对C的函数指针,只是多做了一个查表动作。
4. 单例的优缺点
优点:
1:一个类只被实例化一次,提供了对唯一实例的受控访问。
2:节省系统资源
3:允许可变数目的实例。
缺点:
1:一个类只有一个对象,可能造成费任过重,在一定程度上违背了“单一职费原则”。
2:由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
3:滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会 导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。