ARC和Non-ARC下的單例模式

原文:http://www.cocoachina.com/industry/20130510/6168.htmlhtml

         http://www.justinyan.me/post/1306ios

單例模式
iOS SDK中也有許多類使用了單例模式,例如, UIApplication:當程序啓動的時候,會調用UIApplicationMain方法,在該方法中,會實例化一個UIApplication對象,以後在程序中的任意地方調用sharedApplication方法都將返回一個與當前應用程序相關的UIApplication實例(UIApplicationMain方法中建立的UIApplication單例)。

 

何時使用單例模式?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

1、問題起源

一切起源於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 的這篇博文有比較詳細的討論,包括了線程安全的考慮,有興趣的童鞋能夠圍觀一下。

2、allocWithZone

首先咱們知道,咱們須要保證單例類只有一個惟一的實例,而平時咱們在初始化一個對象的時候, [[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?

3、NSZone

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在這方面是作了處理了,對開發者透明,無需開發者本身去作。

4、結論

allocWithZone不被Apple鼓勵使用,基本上多數時候程序員也不須要本身去管理本身的zone。固然多瞭解一些東西老是好的嘛。

相關文章
相關標籤/搜索