单例模式
保证一个类只有一个实例,并且提供一个全局的访问入口
系统为我们提供了哪些单例类
UIApplication(应用程序实例类)
NSNotificationCenter(消息中心类)
NSFileManager(文件管理类)
NSUserDefaults(应用程序设置)
NSURLCache(请求缓存类)
NSHTTPCookieStorage(应用程序cookies池)
不同的位置存放的变量以及释放时机
单例对象一旦创建,对象指针编译时分配内存,保存在静态区,程序结束运行时由系统释放,单例对象在堆中分配内存,同样也是在程序结束运行时才会被释放。
单例的优缺点
优:
缺:
单例的两种实现方式
懒汉式
不使用时不创建,第一次使用时再进行创建。
访问量较小时,采用懒汉实现,时间换空间
单线程下实现单例
//为确保通过alloc、copy、mutableCopy创建的对象不会产生新对象
//就需要重写一些方法,对于alloc,需要重写它的底层实现allocWithZone
//而此时我们不能通过当前类调用allocwithZone来创建实例,会死循环
//只能调用父类的allocWithZone
static id instance = nil;
+ (id)shareInstance {
if (instance == nil) {
instance = [super allocWithZone:NULL] init];
}
}
以上代码在单线程下不会出现问题
如果是多线程下,可能出现多个线程进入if中,此时就会创建多个实例对象
我们可以加一个锁,以保证每次只有一个线程访问,一个访问完下一个再进行访问
static id instance = nil;
+ (id)shareInstance {
@synchronized(self) {
if (instance == nil) {
instance = [super allocWithZone:NULL] init];
}
}
}
添加@synchronized后,可以确保每次只有一个线程可以访问,其他线程则进行等待。
但是存在的问题是,只有在instance未被创建时,加锁才是必要的。
如果已经创建,后面的线程仍进行加锁解锁,会影响性能
为此,还可以进行优化,在锁的外部进行判断,防止在对象已被创建,后面的线程进行多次加锁解锁。
static id instance = nil;
+ (id)shareInstance {
//防止多次加锁解锁
if (instance == nil) {
@synchronized(self) {
if (instance == nil) {
instance = [super allocWithZone:NULL] init];
}
}
}
}
但相比@synchronized,GCD中的dispatch_once更高效,它没有使用重量级的同步机制,性能也优于前者
所以,单例推荐写法如下
#import <Foundation/Foundation.h>
@interface Manager : NSObject <NSCopying, NSMutableCopying>
+ (instancetype)shareInstance;
@end
#import "Manager.h"
@implementation Manager
+ (instancetype)shareInstance {
static Manager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[super allocWithZone:NULL] init];
});
return manager;
}
//对下面三种方法的重写是为了保证通过alloc、copy、mutableCopy创建对象也是返回唯一的实例
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [Manager shareInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return [Manager shareInstance];
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return [Manager shareInstance];
}
dispatch_once 无论使用多线程还是单线程,都只执行一次,在安全的前提下也保证了性能。
dispatch_once主要是根据onceToken的值来决定怎么执行代码
- 当onceToken为0时,线程执行dispatch_once的block中的代码
- 当onceToken为-1时,线程跳过dispatch_once的block中的代码
- 当onceToken为其他值时,线程被阻塞,等待onceToken值改变
dispatch_once的执行流程
- 当线程调用shareInstance,此时onceToken为0,执行dispatch_once的block中的代码,此时onceToken中的值为其他值
- 这时如果有其他线程再调用shareInstance方法时,onceToken值为其他值,线程阻塞
- 当block线程执行完block后,onceToken变为-1。其他线程不再阻塞,跳过block
- 下次再调用shareInstance时,onceToken为-1,直接跳过block
饿汉式
类被加载时就创建单例对象,一个类在整个生命周期中只会被加载一次,所以单例类只会创建一个单例对象。
也就是说线程访问之前,单例对象就早已创建好了,访问时直接拿到这个唯一的对象。
在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能,空间换时间
//.m中
static Manager *manager = nil;
+ (instancetype)shareInstance {
return manager;
}
+(void)load {
[super load];
manager = [[super allocWithZone:nil] init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return manager;
}
- (id)copyWithZone:(NSZone *)zone {
return manager;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return manager;
}