在一开始学习OC的时候,我们初步接触过单例模式。在学习定时器与视图移动的控件中,我们初步意识到单例模式的重要性。对于我们需要保持的控件,使用单例可以有效的防止再次创建而造成的bug,现在我们深入学习一下单例模式。
如果一个类有且仅有一个实例,并提供一个类方法供全局调用,那么这个类就被称为单例类。
而使用这样的类,就是单例模式。
优点:
缺点:
实现单例模式,我们需要保证永远只创建一个实例。那么创建实例的方法有四种,因此我们需要分别改写这四种方法。
懒汉模式的主要特点是在需要时才会创建单例对象的实例,而不是在程序启动时就立即创建。通过延迟对象的初始化来节省资源和提高性能。
如同名字,除了在用到时“懒得创建”。
#import "Singletion.h"
@implementation Singletion
static id instance = nil;
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
if(instance == nil) {
@synchronized (self) {
if(instance == nil) {
instance = [super allocWithZone:zone];
}
}
}
return instance;
}
+(instancetype)mySingletion {
if (instance == nil) {
@synchronized (self) {
instance = [[self alloc] init];
}
}
return instance;
}
-(id)copyWithZone:(NSZone *)zone
{
return instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
return instance;
}
验证代码:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import <Foundation/Foundation.h>
#import "Singletion.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
Singletion *singletion = [Singletion mySingletion];
Singletion *copySingletion = [singletion copy];
Singletion *mutableCopySingletion = [singletion mutableCopy];
Singletion *allocSingletion = [[Singletion alloc] init];
NSLog(@"%d",singletion == copySingletion);
NSLog(@"%d",singletion == mutableCopySingletion);
NSLog(@"%d",singletion == allocSingletion);
NSLog(@"%d",copySingletion == mutableCopySingletion);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
结果:
饿汉模式的特点是在一开始就创建单例对象的实例,提前创建单例对象,并且分配好内存空间。使用时就是将该对象拿出来。
#import "Singletion.h"
@implementation Singletion
static id instance = nil;
+ (void)load{
instance = [[self alloc] init];
}
+(instancetype)mySingletion {
if (instance == nil) {
@synchronized (self) {
instance = [[self alloc] init];
}
}
return instance;
}
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
if(instance == nil) {
@synchronized (self) {
if(instance == nil) {
instance = [super allocWithZone:zone];
}
}
}
return instance;
}
-(id)copyWithZone:(NSZone *)zone
{
return instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
return instance;
}
重点就是在程序加载开始,就创建这个单例对象。
判断代码:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import <Foundation/Foundation.h>
#import "Singletion.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
Singletion *singletion = [Singletion sharedInstance];
Singletion *copySingletion = [singletion copy];
Singletion *mutableCopySingletion = [singletion mutableCopy];
Singletion *allocSingletion = [[Singletion alloc] init];
NSLog(@"%d",singletion == copySingletion);
NSLog(@"%d",singletion == mutableCopySingletion);
NSLog(@"%d",singletion == allocSingletion);
NSLog(@"%d",copySingletion == mutableCopySingletion);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
运行结果:
原本在书中的写法为:
这种方法在多线程调用时,并不能保证成功使用单例模式。
+(instancetype)mySingleton {
if (instance == nil) {
instance = [[self alloc] init];
}
return instance;
}
从而我们使用加锁,来保证多线程中也可以创建单例类
加锁写法:
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
if(instance == nil) {
@synchronized (self) {
if(instance == nil) {
instance = [super allocWithZone:zone];
}
}
}
return instance;
}
还有一个GCD的写法,换一种加锁方式。
GCD写法:
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
}
GCD加锁相比于第二个加锁方法,在性能上更有优势。因为它省去了锁操作,代替的是大量的原子操作,直接利用了 lock 的汇编指令,靠底层 CPU 指令来支持的。
dispatch_once 主要是根据 onceToken 的值来决定怎么去执行代码。
1.当 onceToken = 0 时,线程执行 dispatch_once 的 block 中代码;
2.当 onceToken = -1 时,线程跳过 dispatch_once 的 block 中代码不执行;
3.当 onceToken 为其他值时,线程被阻塞,等待 onceToken 值改变。
当线程调用mySingleton方法时,此时 onceToken = 0,调用 block 中的代码,此时 onceToken =其他值。
当其他线程再调用 mySingleton 方法时,onceToken为其他值,线程阻塞。当 block 线程执行完 block之后,onceToken = -1,其他线程不再阻塞,跳过 block。下次再调用mySingleton方法 时, block 已经为-1,直接跳过 block。
饿汉模式和懒汉模式都是实现单例模式的一种方法,各有优点。
饿汉模式用空间换取时间,先创建出来,这样再调用时就不需要花时间判断,节省运行时间。
懒汉模式用时间换空间,如果一直不需要创建这个单例,则节约了内存空间。一旦进行判断是否需要创建该实例,则会浪费判断的时间。
我们还需要关注这里通过加锁的方式创建单例,以保证线程安全。这是因为如果到了多线程的环境里,多个进程同时访问单例,该单例模式也有可能返回不同的对象。因此我们可以通过加锁和GCD模式,来实现保证线程安全。