原文:http://www.cocoachina.com/industry/20130510/6168.htmlhtml
http://www.justinyan.me/post/1306ios
何時使用單例模式?git
在程序中,單例模式常常用於只但願一個類只有一個實例,而不運行一個類還有兩個以上的實例。固然,在iOS SDK中,根據特定的需求,有些類不只提供了單例訪問的接口,還爲開發者提供了實例化一個新的對象接口,例如,NSFileManager能夠經過defaultManager方法返回相同的一個NSFileManager對象。若是須要新的一個NSFileManager實例對象,能夠經過init方法。程序員
iOS中單例模式的實現github
iOS中單例模式的實現方式通常分爲兩種:Non-ARC(非ARC)和ARC+GCD。安全
1.Non-ARC(非ARC)
非ARC的實現方法以下所示:app
BVNonARCSingleton.hide
@interface BVNonARCSingleton : NSObject @property ( nonatomic, retain) NSString *tempProperty; + (BVNonARCSingleton *)sharedInstance; @end
BVNonARCSingleton.mpost
@implementation BVNonARCSingleton static BVNonARCSingleton *sharedInstance = nil; // 獲取一個sharedInstance實例,若是有必要的話,實例化一個 + (BVNonARCSingleton *)sharedInstance { if (sharedInstance == nil) { sharedInstance = [[super allocWithZone:NULL] init]; } return sharedInstance; } // 當第一次使用這個單例時,會調用這個init方法。 - (id)init { self = [super init]; if (self) { // 一般在這裏作一些相關的初始化任務 } return self; } // 這個dealloc方法永遠都不會被調用--由於在程序的生命週期內容,該單例一直都存在。(因此該方法能夠不用實現) -(void)dealloc { [super dealloc]; } // 經過返回當前的sharedInstance實例,就能防止實例化一個新的對象。 + (id)allocWithZone:(NSZone*)zone { return [[self sharedInstance] retain]; } // 一樣,不但願生成單例的多個拷貝。 - (id)copyWithZone:(NSZone *)zone { return self; } // 什麼也不作——該單例並不須要一個引用計數(retain counter) - (id)retain { return self; } // 替換掉引用計數——這樣就永遠都不會release這個單例。 - (NSUInteger)retainCount { return NSUIntegerMax; } // 該方法是空的——不但願用戶release掉這個對象。 - (oneway void)release { } //除了返回單例外,什麼也不作。 - (id)autorelease { return self; } @end
實際上上面的代碼蘋果官網也有提供:Creating a Singleton Instance,只不過沒有給出頭文件的定義。上面用非ARC實現單例的方法是線程不安全的,若是有多個線程同時調用sharedInstance方法獲取一個實例,而sharedInstance方法須要花費1-2秒鐘的時間,那麼BVNonARCSingleton的init方法就可能會被屢次調用,也就是不一樣線程得到的BVNonARCSingleton有可能不是同一個實例。怎麼解決線程的不安全呢?答案是使用@synchronized來建立互斥鎖便可。優化
// 保證在實例化的時候是線程安全的(固然,該方法不能保證該單例中全部方法的調用都是線程安全的) @synchronized (self) { if(sharedInstance == nil) { sharedInstance = [[super allocWithZone:NULL] init]; } }
經過上面的代碼就能保存線程安全。
提醒:在iOS中,通常不建議使用非ARC來實現單例模式。更好的方法是使用ARC+GCD來實現。
2.ARC+GCD
經過ARC+GCD的方法來實現單例模式的很是簡單的。下面先來看看具體實現:
BVARCSingleton.h
@interface BVARCSingleton : NSObject @property ( nonatomic, weak) NSString *tempProperty; + (BVARCSingleton *)sharedInstance; @end
BVARCSingleton.m
@implementation BVARCSingleton + (BVARCSingleton *) sharedInstance { static BVARCSingleton *sharedInstance = nil ; static dispatch_once_t onceToken; // 鎖 dispatch_once (& onceToken, ^ { // 最多調用一次 sharedInstance = [[self alloc] init]; }); return sharedInstance; } // 當第一次使用這個單例時,會調用這個init方法。 - (id)init { self = [super init]; if (self) { // 一般在這裏作一些相關的初始化任務 } return self; } @end
在上面的代碼中,調用Grand Central Dispatch (GCD)中的dispatch_once方法就能夠確保BVARCSingleton只被實例化一次。而且該方法是線程安全的,咱們不用擔憂在不一樣的線程中,會得到不一樣的實例。(固然,該方法一樣不能保證該單例中全部方法的調用都是線程安全的)。
固然,在ARC中,不用GCD也是能夠作到線程安全的,跟以前非ARC代碼中使用@synchronized同樣,以下代碼:
// 不使用GCD,經過@synchronized @synchronized (self) { if(sharedInstance == nil) { sharedInstance = [[self alloc] init]; } }
爲了簡化使用ARC+GCD來建立單例,能夠定義下面這樣的一個宏:
#define DEFINE_SHARED_INSTANCE_USING_BLOCK(block) \ static dispatch_once_t onceToken = 0; \ __strong static id sharedInstance = nil; \ dispatch_once(&onceToken, ^{ \ sharedInstance = block(); \ }); \ return sharedInstance;
實例化的實現方法以下所示:
+ (BVARCSingleton *) sharedInstance { DEFINE_SHARED_INSTANCE_USING_BLOCK(^{ return [[self alloc] init]; }); }
單例的使用
單例的使用方法很簡單,在代碼中的任意位置,以下使用便可:
在BVAppDelegate.m中添加頭文件:
#import "BVNonARCSingleton.h"
#import "BVARCSingleton.h"
以下使用方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [BVNonARCSingleton sharedInstance].tempProperty = @"非ARC單例的實現"; NSLog(@"%@", [BVNonARCSingleton sharedInstance].tempProperty); [BVARCSingleton sharedInstance].tempProperty = @"ARC單例的實現"; NSLog(@"%@", [BVARCSingleton sharedInstance].tempProperty); return YES; }
運行程序,會在控制檯窗口輸出以下內容:
1.2013-05-09 16:44:07.649 SingletonPattern[5159:c07] 非ARC單例的實現
2.2013-05-09 16:44:33.204 SingletonPattern[5159:c07] ARC單例的實現
alloc和allocwithzone
一切起源於Apple官方文檔裏面關於單例(Singleton)的示範代碼:Creating a Singleton Instance.
主要的爭議集中在下面這一段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
static
MyGizmoClass *sharedGizmoManager =
nil
;
+ (MyGizmoClass*)sharedManager
{
if
(sharedGizmoManager ==
nil
) {
sharedGizmoManager = [[
super
allocWithZone:
NULL
] init];
}
return
sharedGizmoManager;
}
+ (
id
)allocWithZone:(
NSZone
*)zone
{
return
[[
self
sharedManager] retain];
}
|
其中:
1
|
sharedGizmoManager = [[
super
allocWithZone:
NULL
] init];
|
這段有另外一個版本,不使用 allocWithZone 而是直接 alloc,以下:
1
|
sharedGizmoManager = [[
super
alloc] init];
|
這就引起了一個討論,爲何要覆蓋allocWithZone方法,到底 alloc 和 allocWithZone 有啥區別呢?
PS:關於ObjC單例的實現,@Venj 的這篇博文有比較詳細的討論,包括了線程安全的考慮,有興趣的童鞋能夠圍觀一下。
首先咱們知道,咱們須要保證單例類只有一個惟一的實例,而平時咱們在初始化一個對象的時候, [[Class alloc] init],實際上是作了兩件事。 alloc 給對象分配內存空間,init是對對象的初始化,包括設置成員變量初值這些工做。而給對象分配空間,除了alloc方法以外,還有另外一個方法: allocWithZone.
在NSObject 這個類的官方文檔裏面,allocWithZone方法介紹說,該方法的參數是被忽略的,正確的作法是傳nil或者NULL參數給它。而這個方法之因此存在,是歷史遺留緣由。
Do not override allocWithZone: to include any initialization code. Instead, class-specific versions of init… methods.
This method exists for historical reasons; memory zones are no longer used by Objective-C.
文檔裏面提到,memory zone已經被棄用了,只是歷史緣由才保留這個接口。詳細是什麼歷史緣由我沒找到,不事後面介紹的內容會稍微涉及到。
而實踐證實,使用alloc方法初始化一個類的實例的時候,默認是調用了 allocWithZone 的方法。因而覆蓋allocWithZone方法的緣由已經很明顯了:爲了保持單例類實例的惟一性,須要覆蓋全部會生成新的實例的方法,若是有人初始化這個單例類的時候不走[[Class alloc] init] ,而是直接 allocWithZone, 那麼這個單例就再也不是單例了,因此必須把這個方法也堵上。allocWithZone的答案到此算是解決了,可是,問題是無止境的。
這裏引出了另一個問題: What the hell is Memory Zone?
Apple官方文檔裏面就簡單的幾句,吝嗇得很:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
NSZone
Used to identify and manage memory zones.
typedef
struct
_NSZone
NSZone
;
Availability
Available in OS X v10.0 and later.
Declared In
NSZone
.h
|
CocaDev的wiki就寫得詳細的多了,原文地址在這裏:http://cocoadev.com/wiki/NSZone
大意上是說NSZone是Apple用來分配和釋放內存的一種方式,它不是一個對象,而是使用C結構存儲了關於對象的內存管理的信息。基本上開發者是不須要去理會這個東西的,cocoa Application使用一個系統默認的NSZone來對應用的對象進行管理。那麼在何時你會想要有一個本身控制的NSZone呢?當默認的NSZone裏面管理了大量的對象的時候。這種時候,大量對象的釋放可能會致使內存嚴重碎片化,cocoa自己有作過優化,每次alloc的時候會試圖去填滿內存的空隙,可是這樣作的話時間的開銷很大。因而乎,你能夠本身建立一個NSZone,這樣當你有大量的alloc請求的時候就所有轉移到指定的NSZone裏面去,減小了大量的時間開銷。並且,使用NSZone還能夠一口氣把你建立的zone裏面的東西都清除掉,省掉了大量的時間去一個個dealloc對象。
總的來講,當你須要建立大量的對象的時候,使用NSZone仍是能節省一些時間的,不過前提是你得知道怎麼去用它。這篇wiki裏面也寫了NSZone的用法,感興趣的童鞋能夠看看,不過另外一篇2002年的文章就說開發者已經不能建立一個真正的NSZone了(看來也許這就是歷史緣由了),只能建立main zone的一個child zone。文章在這裏:http://www.cocoabuilder.com/archive/cocoa/65056-what-an-nszone.html#65056 Timothy J.wood 的回答。
Timothy還講到若是可使用NSZone的話,多個對象在同一時間alloc能夠減小分頁使用,並且在同一個時間dealloc能夠減小內存碎片。想必後來Apple在這方面是作了處理了,對開發者透明,無需開發者本身去作。
allocWithZone不被Apple鼓勵使用,基本上多數時候程序員也不須要本身去管理本身的zone。固然多瞭解一些東西老是好的嘛。