引言
首先,小汤我在这里,要表示一下歉意,本来是想要每天写一篇Swift的学习小tip的,无奈最近手头的money花差的差不多了,只能迫不得已,出门找工作去了,没能履行承诺之处还请大家见谅.
那么,废话不多说了,开始我们今天的主题: 单例 !
##单例介绍:
说到单例,大家应该都不陌生,在传说中的那23种 (为啥我就会6种捏o(╯□╰)o…) 设计模式中,单例应该是属于和简单工厂模式并列的最简单的设计模式了,也应该是最常用的.
像这样简单易懂,又能有效提高程序运行效率的设计模式,作为一个iOS程序员,必然是十分熟练的啦.
今天啊,小汤我就给大家介绍一下在Objective-C中,我们常用的单例模式的写法,以及小汤我在研究其中某种写法时,写出来的一个效率更高的写法.
当然啦,MRC下的写法,我就不多说了,已经有那么多大牛写过了,我就简化一下,直接写在ARC下的写法啦,MRC可以直接把相关代码套用过去就行喽~
##网上流传的Objective-C的单例写法:
1 2 3 4 5 6 7 8 9 10 11
| + (instancetype)sharedPerson0{ static id instance0 = nil; static BOOL once0 = YES; @synchronized(self){ if (once0) { instance0 = [[Person alloc]init]; once0 = NO; } } return instance0; }
|
以上就是网上流传已久的单例模式的写法啦.
##通过GCD实现的单例模式写法:
1 2 3 4 5 6 7 8
| + (instancetype)sharedPerson1{ static id instance1 = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance1 = [[self alloc]init]; }); return instance1; }
|
这是GCD方式,也就是使用dispatch_once实现的单例模式的写法.
首先展示一下两者是不是都实现了单例呢?为此,小汤我新建了一个Person类,在其中实现了这两种方法,然后在控制器启动的时候执行了下面两段代码
1 2 3 4 5 6 7 8
| for (int i = 0; i < 10; i++) { NSLog(@"--单例方法0:%@",[Person sharedPerson0]); } NSLog(@"-----"); for (int i = 0; i < 10; i++) { NSLog(@"--单例方法1:%@",[Person sharedPerson1]); } NSLog(@"-----");
|
执行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 2015-06-06 14:46:35.906 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.907 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.907 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.907 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.907 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.907 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.907 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.907 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.907 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.908 test[966:22855] --单例方法0:<Person: 0x7f9c19418740> 2015-06-06 14:46:35.908 test[966:22855] ----- 2015-06-06 14:46:35.908 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.908 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.908 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.908 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.908 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.908 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.960 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.960 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.960 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.960 test[966:22855] --单例方法1:<Person: 0x7f9c1961e510> 2015-06-06 14:46:35.960 test[966:22855] -----
|
可以看到这两种方式写的单例模式都是能够实现我们的需求的.
那么两者有什么区别呢?
下面我们来看一看两者在运行时间上的区别:
1 2 3 4 5 6 7 8 9 10 11
| CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < 1000000; ++i) { [Person sharedPerson0]; } NSLog(@"====方法0耗时:%f",CFAbsoluteTimeGetCurrent() - start); start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < 1000000; ++i) { [Person sharedPerson1]; } NSLog(@"====方法1耗时:%f",CFAbsoluteTimeGetCurrent() - start);
|
我通过上面这两个方法,比较两个单例模式在分别实例化100万个对象的耗时,结果如下:
1 2
| 2015-06-06 14:50:47.899 test[1009:24267] ====方法0耗时:0.184217 2015-06-06 14:50:47.981 test[1009:24267] ====方法1耗时:0.081377
|
可以看到,方法1的耗时明显要少于方法二的耗时,那么为什么GCD能够做到这一点呢?
小汤思考之后,觉得应该是@synchronized这个锁对性能的消耗十分明显.
而在打印了dispatch_once这个方法的入参onceToken之后,发现,在实例化这个对象之前,onceToken的值为0,而之后变为-1.
于是,在这个基础上,小汤我想到了一个方法来减少这种性能消耗.
那么问题来了? dispatch_once会是通过小汤我想象的这样做的么?
##小汤我的单例实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| + (instancetype)sharedPerson2{ static id instance2 = nil; static BOOL once2 = YES; static BOOL isAlloc = NO; if (!isAlloc) { @synchronized(self){ if (once2) { instance2 = [[Person alloc]init]; once2 = NO; isAlloc = YES; } } } return instance2; }
|
我在进行同步锁之前,再进行了一次判断,这样会导致什么后果呢?
很显然,由于内部有互斥锁,那么在实例化对象时,肯定只有一个对象被实例化,然后在实例化对象之后,由于内部存在一个判断,那么就不会再有其他的对象被实例化,而在外面的这个判断,又能在下一次外部变量进行访问的时候直接返回值,提高了效率.
说了那么多,先来测试一下小汤我的代码是不是能够创建一个单例呢?
测试代码:
1 2 3 4
| for (int i = 0; i < 10; i++) { NSLog(@"--单例方法2:%@",[Person sharedPerson2]); } NSLog(@"-----");
|
测试结果:
1 2 3 4 5 6 7 8 9 10 11
| 2015-06-06 15:01:40.412 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.412 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.412 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.412 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.412 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.413 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.413 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.413 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.413 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.413 test[1081:26913] --单例方法2:<Person: 0x7fd891553e20> 2015-06-06 15:01:40.413 test[1081:26913] -----
|
以上结果可以显示,小汤我的单例也是可行的.那么我们来对比一下我的这个实现和GCD的那种实现方式是不是一样呢?
效率测试代码:
1 2 3 4 5 6 7 8 9 10 11
| CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < 1000000; ++i) { [Person sharedPerson1]; } NSLog(@"====方法1耗时:%f",CFAbsoluteTimeGetCurrent() - start); start = CFAbsoluteTimeGetCurrent(); for (int i = 0; i < 1000000; ++i) { [Person sharedPerson2]; } NSLog(@"====方法2耗时:%f",CFAbsoluteTimeGetCurrent() - start);
|
还是比较100万次,我们来看看效率如何呢?
测试结果:
1 2
| 2015-06-06 15:04:58.696 test[1125:28301] ====方法1耗时:0.089754 2015-06-06 15:04:58.763 test[1125:28301] ====方法2耗时:0.065470
|
结果是不是很吃惊?! 我 也 表 示 很 吃 惊 !
没有想到小汤我写的单例的效率居然比dispatch_once的效率还要略高那么一丝.
当然,这个只是让小汤我略微嘚瑟了一下,重点是,小汤我还是没想清楚,GCD下的这个dispatch_once到底是怎么实现的呢?
是不是还存在优化的可能呢?希望对此有研究的各位大牛给个答案哈~
本作品采用 署名-非商业性使用-相同方式共享 2.5 中国大陆 (CC BY-NC-SA 2.5)协议
进行许可,欢迎转载,但转载请注明来自SarielTang
,并保持转载后文章内容的完整。本人保留所有版权相关权利。
本文永久链接:http://sarieltang.github.io/2015/06/06/知识点小结/2015-06-06/index/