设计模式系列 14-- 单例模式

1,288 阅读4分钟

在开发中经常会用到单例设计模式,目的就是为了在程序的整个生命周期内,只会创建一个类的实例对象,而且只要程序不被杀死,该实例对象就不会被释放。下面我们来看看单例的概念、用途、如何创建,以便加深理解。


定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。


作用

  • 在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在APP开发中我们可能在任何地方都要使用用户的信息,那么可以在登录的时候就把用户信息存放在一个文件里面,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
  • 有的情况下,某个类可能只能有一个实例。比如说你写了一个类用来播放音乐,那么不管任何时候只能有一个该类的实例来播放声音。再比如,一台计算机上可以连好几个打印机,但是这个计算机上的打印程序只能有一个,这里就可以通过单例模式来避免两个打印任务同时输出到打印机中,即在整个的打印过程中我只有一个打印程序的实例。

创建单例

有两种方法来创建单例,下面分别介绍

1、GCD方式创建单例

static id _instance;   

+ (instancetype)allocWithZone:(struct _NSZone *)zone  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [super allocWithZone:zone];   
    });   
    return _instance;   
}   

+ (instancetype)sharedInstance  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [[self alloc] init];   
    });   
    return _instance;   
}   

- (id)copyWithZone:(NSZone *)zone   
{   
    return _instance;   
}  

- (id)mutableCopyWithZone:(NSZone *)zone {   
    return _instance;   
}

2、互斥锁方式

static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}
+ (instancetype)sharedInstance
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
    }
    return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}

上面两种方式都可以创建单例,而且保证了用户不管是通过shareInstance方法,还是alloc、copy方法得到的实例都是一样的。

上面代码的关键之处就在于如何在多线程情况下保证创建的单例还是同一个。

我们先看看在GCD情况下,如果不使用dispatch_once和同步锁创建单例会出现什么问题,去掉两者后创建单例的代码如下

+ (instancetype)sharedInstance  
{   
 if (_instance == nil) {
     _instance = [[self alloc] init];
  }
}

假设此时有两条线程:线程1和线程2,都在调用shareInstance方法来创建单例,那么线程1运行到if (_instance == nil)出发现_instance = nil,那么就会初始化一个_instance,假设此时线程2也运行到if的判断处了,此时线程1还没有创建完成实例_instance,所以此时_instance = nil还是成立的,那么线程2又会创建一个_instace。

此时就创建了两个实例对象,导致问题。

解决办法1、使用dispatch_once

dispatch_once保证程序在运行过程中只会被运行一次,那么假设此时线程1先执行shareInstance方法,创建了一个实例对象,线程2就不会再去执行dispatch_once的代码了。从而保证了只会创建一个实例对象。

解决办法2、使用互斥锁

假设此时线程1在执行shareInstance方法,那么synchronize大括号内创建单例的代码,如下所示:

if (_instance == nil) {
            _instance = [[self alloc] init];
        }

就会被当做一个任务被加上了一把锁。此时假设线程2也想执行shareInstance方法创建单例,但是看到了线程1加的互斥锁,就会进入睡眠模式。等到线程1执行完毕,才会被唤醒,然后去执行上面所示的创建单例的代码,但是此时_instance !=nil,所以不会再创建新的实例对象了。从而保证只会创建一个实例对象。

但是互斥锁会影响性能,所以最好还是使用GCD方式创建单例。


宏创建单例

如果我们需要在程序中创建多个单例,那么需要在每个类中都写上一次上述代码,非常繁琐。

我们可以使用宏来封装单例的创建,这样任何类需要创建单例,只需要一行代码就搞定了。

实现代码

Singleton.h文件
==================================

#define SingletonH(name) + (instancetype)shared##name;

#define SingletonM(name) \
static id _instance; \
 \
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        _instance = [super allocWithZone:zone]; \
    }); \
    return _instance; \
} \
 \
+ (instancetype)shared##name \
{ \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        _instance = [[self alloc] init]; \
    }); \
    return _instance; \
} \
 \
- (id)copyWithZone:(NSZone *)zone \
{ \
    return _instance; \
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone { \
    return _instance; \
}

如何调用

假设我们要在类viewcontroller中使用,调用方法如下:

viewcontroller.h文件
===========================

#import <UIKit/UIKit.h>
#import "Singleton.h"

@interface ViewController : UIViewController
SingletonH(viewController)
@end



viewcontroller.m文件
===========================

@interface ViewController ()

@end

@implementation ViewController

SingletonM(ViewController)

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"%@ %@ %@ %@", [ViewController sharedViewController],[ViewController sharedViewController], [[ViewController alloc] init],[[ViewController alloc] init]);

}

@end

输出结果

 <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00>

可以看到四个对象的内存地址完全一样,说明是同一个对象