首先,小湯我在這里,要表示一下歉意,本來是想要每天寫一篇Swift的學習小tip的,無奈近期手頭的money花差的差點兒相同了,僅僅能迫不得已,出門找工作去了,沒能履行承諾之處還請大家見諒.
那么,廢話不多說了,開始我們今天的主題: 單例 !
單例介紹:
說到單例,大家應該都不陌生,在傳說中的那23種 (為啥我就會6種捏o(╯□╰)o…) 設計模式中,單例應該是屬于和簡單工廠模式并列的最簡單的設計模式了,也應該是最經常使用的.
像這樣簡單易懂,又能有效提高程序執行效率的設計模式,作為一個iOS程序猿,必定是十分熟練的啦.
今天啊,小湯我就給大家介紹一下在Objective-C中,我們經常使用的單例模式的寫法,以及小湯我在研究當中某種寫法時,寫出來的一個效率更高的寫法.
當然啦,MRC下的寫法,我就不多說了,已經有那么多大牛寫過了,我就簡化一下,直接寫在ARC下的寫法啦,MRC能夠直接把相關代碼套用過去即可嘍~
網上流傳的Objective-C的單例寫法:
+ (instancetype)sharedPerson0{static id instance0 = nil;static BOOL once0 = YES;@synchronized(self){if (once0) {instance0 = [[Person alloc]init];once0 = NO;}}return instance0;}
以上就是網上流傳已久的單例模式的寫法啦.
通過GCD實現的單例模式寫法:
+ (instancetype)sharedPerson1{static id instance1 = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance1 = [[self alloc]init];});return instance1;}
這是GCD方式,也就是使用dispatch_once實現的單例模式的寫法.
首先展示一下兩者是不是都實現了單例呢?為此,小湯我新建了一個Person類,在當中實現了這兩種方法,然后在控制器啟動的時候執行了以下兩段代碼
for (int i = 0; i < 10; i++) {NSLog(@"--單例方法0:%@",[Person sharedPerson0]);}NSLog(@"-----");for (int i = 0; i < 10; i++) {NSLog(@"--單例方法1:%@",[Person sharedPerson1]);}NSLog(@"-----");
執行結果例如以下:
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] -----
能夠看到這兩種方式寫的單例模式都是能夠實現我們的需求的.
那么兩者有什么差別呢?
以下我們來看一看兩者在執行時間上的差別:
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萬個對象的耗時,結果例如以下:
2015-06-06 14:50:47.899 test[1009:24267] ====方法0耗時:0.1842172015-06-06 14:50:47.981 test[1009:24267] ====方法1耗時:0.081377
能夠看到,方法1的耗時明顯要少于方法二的耗時,那么為什么GCD能夠做到這一點呢?
小湯思考之后,認為應該是@synchronized這個鎖對性能的消耗十分明顯.
而在打印了dispatch_once這種方法的入參onceToken之后,發現,在實例化這個對象之前,onceToken的值為0,而之后變為-1.
于是,在這個基礎上,小湯我想到了一個方法來降低這樣的性能消耗.
那么問題來了?
dispatch_once會是通過小湯我想象的這樣做的么?
小湯我的單例實現:
+ (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;}
我在進行同步鎖之前,再進行了一次推斷,這樣會導致什么后果呢?
非常顯然,因為內部有相互排斥鎖,那么在實例化對象時,肯定僅僅有一個對象被實例化,然后在實例化對象之后,因為內部存在一個推斷,那么就不會再有其它的對象被實例化,而在外面的這個推斷,又能在下一次外部變量進行訪問的時候直接返回值,提高了效率.
說了那么多,先來測試一下小湯我的代碼是不是能夠創建一個單例呢?
測試代碼:
for (int i = 0; i < 10; i++) {NSLog(@"--單例方法2:%@",[Person sharedPerson2]);}NSLog(@"-----");
測試結果:
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的那種實現方式是不是一樣呢?
效率測試代碼:
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萬次,我們來看看效率怎樣呢?
測試結果:
2015-06-06 15:04:58.696 test[1125:28301] ====方法1耗時:0.0897542015-06-06 15:04:58.763 test[1125:28301] ====方法2耗時:0.065470
結果是不是非常驚訝?! 我 也 表 示 非常 吃 驚 !
沒有想到小湯我寫的單例的效率竟然比dispatch_once的效率還要略高那么一絲.
當然,這個僅僅是讓小湯我稍微嘚瑟了一下,重點是,小湯我還是沒想清楚,GCD下的這個dispatch_once究竟是怎么實現的呢?
是不是還存在優化的可能呢?希望對此有研究的各位大牛給個答案哈~