iOS单例详解

    xiaoxiao2021-03-31  36

    1、GCD方式创建单例

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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、互斥锁方式

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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和同步锁创建单例会出现什么问题,去掉两者后创建单例的代码如下

    1 2 3 4 5 6 + (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大括号内创建单例的代码,如下所示:

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

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

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

    宏创建单例

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

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

    实现代码

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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中使用,调用方法如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 viewcontroller.h文件 =========================== #import #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

    输出结果

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

    转载请注明原文地址: https://ju.6miu.com/read-665269.html

    最新回复(0)