單例模式在iOS中的應用——三種創建單例方法對比

單例模式優點

  1. 單例模式在內存中只有一個實例,減少了內存開支。特別是一個對象需要頻繁的創建、銷燬時,而創建與銷燬的性能有無法優化,單例模式的優勢就非常明顯。
  2. 單例模式只生成一個實例,減少了系統性能開銷,當一個對象的產生需要比較多的資源時,如讀取配置、產生其他依賴對象時,則可以通過在應用啓動時直接產生一個單例對象,然後永久駐留內存的方式來解決。
  3. 單例模式可以避免對資源的多重佔用。
  4. 單例模式可以在系統設置全局的訪問點,優化和共享資源訪問。

單例模式缺點

  1. 單例模式一般沒有接口,擴展很困難,除了修改代碼基本上沒有第二種途徑實現
  2. 單例模式對測試是不利的。在並行開發環境中,如果單例模式沒有完成,是不能進行測試的。
  3. 單例模式與單一職責原則有衝突。

單例模式在iOS中的使用

單例模式在iOS開發中的使用還是蠻多的,許多FoundationCocoaUIKit中的類都實現了單例模式,比如應用程序本身UIApplication、文件操作類NSFileManager、消息中心NSNotificitonCenter等系統都已經給我們實現單例,我們只需要使用就好了。在iOS中使用單例模式要使用類方法,通過類方法返回該類的唯一對象。

我知道的在iOS開發實現單例模式主要有以下三種方式:


第一種

該方法是蘋果的官方文檔中寫的一種方式,通過覆蓋NSObject的部分方法實現,使該類無法allocretainrelease。這是最麻煩的一種方法,也是最不好的一種方法

static Singleton *instance = nil;

+ (Singleton *)sharedInstance
{
    if (instance == nil) {
        instance = [[super allocWithZone:NULL] init];
    }
    return instance;
}

+ (id)allocWithZone:(NSZone *)zone
{
    return [[self sharedInstance] retain];
}

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

- (id)retain
{
    return self;
}

- (NSUInteger)retainCount
{
    return NSUIntegerMax;  //denotes an object that cannot be released
}

- (void)release
{
    //do nothing
}

- (id)autorelease
{
    return self;
}
可以看到這種方式,使用靜態成員維持了一個永久存在的對象,而且覆蓋了alloc方法(alloc方法會調用allocWithZone:方法),並且也覆蓋了所有與引用技術有關的方法,這都使這個對象不會被銷燬。這樣看上去基本實現了我們需要的,但是寫起來麻煩不說,還有很大的一個問題,那就是多線程問題,如果是在多線程中那麼該種方法就不能保證只產生一個對象了。所以這種方式只是介紹一下,並不推薦使用。


第二種

第二種跟第一種差不多,也是通過覆蓋NSObject的方法實現的,但是它在第一種的基礎上增加了多線程的處理,所以即使在多線程下,該種方法創建的對象也是唯一的。這種方法已經有大牛爲我們寫好了,全都都是通過C的宏定義#define出來了。現給出該頭文件:

//
//  SynthesizeSingleton.h
//
// Modified by Karl Stenerud starting 16/04/2010.
// - Moved the swizzle code to allocWithZone so that non-default init methods may be
//   used to initialize the singleton.
// - Added "lesser" singleton which allows other instances besides sharedInstance to be created.
// - Added guard ifndef so that this file can be used in multiple library distributions.
// - Made singleton variable name class-specific so that it can be used on multiple classes
//   within the same compilation module.
//
//  Modified by CJ Hanson on 26/02/2010.
//  This version of Matt's code uses method_setImplementaiton() to dynamically
//  replace the +sharedInstance method with one that does not use @synchronized
//
//  Based on code by Matt Gallagher from CocoaWithLove
//
//  Created by Matt Gallagher on 20/10/08.
//  Copyright 2009 Matt Gallagher. All rights reserved.
//
//  Permission is given to use this source code file without charge in any
//  project, commercial or otherwise, entirely at your risk, with the condition
//  that any redistribution (in part or whole) of source code must retain
//  this copyright and permission notice. Attribution in compiled projects is
//  appreciated but not required.
//

#ifndef SYNTHESIZE_SINGLETON_FOR_CLASS

#import <objc/runtime.h>


#pragma mark -
#pragma mark Singleton

/* Synthesize Singleton For Class
 *
 * Creates a singleton interface for the specified class with the following methods:
 *
 * + (MyClass*) sharedInstance;
 * + (void) purgeSharedInstance;
 *
 * Calling sharedInstance will instantiate the class and swizzle some methods to ensure
 * that only a single instance ever exists.
 * Calling purgeSharedInstance will destroy the shared instance and return the swizzled
 * methods to their former selves.
 *
 *
 * Usage:
 *
 * MyClass.h:
 * ========================================
 *      #import "SynthesizeSingleton.h"
 *
 *      @interface MyClass: SomeSuperclass
 *      {
 *              ...
 *      }
 *      SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(MyClass);
 *
 *      @end
 * ========================================
 *
 *
 *      MyClass.m:
 * ========================================
 *      #import "MyClass.h"
 *
 *      // This line is optional. Use it if you've enabled GCC_WARN_UNDECLARED_SELECTOR
 *      SYNTHESIZE_SINGLETON_FOR_CLASS_PROTOTYPE(MyClass);
 *
 *      @implementation MyClass
 *
 *      SYNTHESIZE_SINGLETON_FOR_CLASS(MyClass);
 *
 *      ...
 *
 *      @end
 * ========================================
 *
 *
 * Note: Calling alloc manually will also initialize the singleton, so you
 * can call a more complex init routine to initialize the singleton like so:
 *
 * [[MyClass alloc] initWithParam:firstParam secondParam:secondParam];
 *
 * Just be sure to make such a call BEFORE you call "sharedInstance" in
 * your program.
 */

#define SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(SS_CLASSNAME)     \
\
+ (SS_CLASSNAME*) sharedInstance;       \
+ (void) purgeSharedInstance;


#define SYNTHESIZE_SINGLETON_FOR_CLASS_PROTOTYPE(SS_CLASSNAME) \
@interface SS_CLASSNAME (SynthesizeSingletonPrivate)    \
- (NSUInteger)retainCountDoNothing;     \
- (NSUInteger)retainCountDoSomething;   \
- (void)releaseDoNothing;       \
- (void)releaseDoSomething;     \
- (id)autoreleaseDoNothing;     \
- (id)autoreleaseDoSomething; \
@end

#define SYNTHESIZE_SINGLETON_FOR_CLASS(SS_CLASSNAME)    \
\
static volatile SS_CLASSNAME* _##SS_CLASSNAME##_sharedInstance = nil;   \
\
+ (volatile SS_CLASSNAME*) sharedInstanceNoSynch        \
{       \
return (volatile SS_CLASSNAME*) _##SS_CLASSNAME##_sharedInstance;       \
}       \
\
+ (volatile SS_CLASSNAME*) sharedInstanceSynch  \
{       \
@synchronized(self)     \
{       \
if(nil == _##SS_CLASSNAME##_sharedInstance)     \
{       \
_##SS_CLASSNAME##_sharedInstance = [[self alloc] init]; \
}       \
}       \
return (volatile SS_CLASSNAME*) _##SS_CLASSNAME##_sharedInstance;       \
}       \
\
+ (volatile SS_CLASSNAME*) sharedInstance       \
{       \
return (volatile SS_CLASSNAME*)[self sharedInstanceSynch]; \
}       \
\
+ (id)allocWithZone:(NSZone*) zone      \
{       \
@synchronized(self)     \
{       \
if (nil == _##SS_CLASSNAME##_sharedInstance)    \
{       \
_##SS_CLASSNAME##_sharedInstance = [super allocWithZone:zone];  \
if(nil != _##SS_CLASSNAME##_sharedInstance)     \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceNoSynch));  \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
method_setImplementation(class_getInstanceMethod(self, @selector(retainCount)), class_getMethodImplementation(self, @selector(retainCountDoNothing)));  \
method_setImplementation(class_getInstanceMethod(self, @selector(release)), class_getMethodImplementation(self, @selector(releaseDoNothing)));  \
method_setImplementation(class_getInstanceMethod(self, @selector(autorelease)), class_getMethodImplementation(self, @selector(autoreleaseDoNothing)));  \
}       \
}       \
}       \
return (id)_##SS_CLASSNAME##_sharedInstance;    \
}       \
\
+ (void)purgeSharedInstance     \
{       \
@synchronized(self)     \
{       \
if(nil != _##SS_CLASSNAME##_sharedInstance)     \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceSynch));    \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
method_setImplementation(class_getInstanceMethod(self, @selector(retainCount)), class_getMethodImplementation(self, @selector(retainCountDoSomething)));        \
method_setImplementation(class_getInstanceMethod(self, @selector(release)), class_getMethodImplementation(self, @selector(releaseDoSomething)));        \
method_setImplementation(class_getInstanceMethod(self, @selector(autorelease)), class_getMethodImplementation(self, @selector(autoreleaseDoSomething)));        \
[_##SS_CLASSNAME##_sharedInstance release];     \
_##SS_CLASSNAME##_sharedInstance = nil; \
}       \
}       \
}       \
\
- (id)copyWithZone:(NSZone *)zone       \
{       \
return self;    \
}       \
\
- (id)retain    \
{       \
return self;    \
}       \
\
- (NSUInteger)retainCount       \
{       \
NSAssert1(1==0, @"SynthesizeSingleton: %@ ERROR: -(NSUInteger)retainCount method did not get swizzled.", self); \
return NSUIntegerMax;   \
}       \
\
- (NSUInteger)retainCountDoNothing      \
{       \
return NSUIntegerMax;   \
}       \
- (NSUInteger)retainCountDoSomething    \
{       \
return [super retainCount];     \
}\
\
- (oneway void)release  \
{       \
NSAssert1(1==0, @"SynthesizeSingleton: %@ ERROR: -(void)release method did not get swizzled.", self);   \
}       \
\
- (void)releaseDoNothing{}      \
\
- (void)releaseDoSomething      \
{       \
@synchronized(self)     \
{       \
[super release];        \
}       \
}       \
\
- (id)autorelease       \
{       \
NSAssert1(1==0, @"SynthesizeSingleton: %@ ERROR: -(id)autorelease method did not get swizzled.", self); \
return self;    \
}       \
\
- (id)autoreleaseDoNothing      \
{       \
return self;    \
}       \
\
- (id)autoreleaseDoSomething    \
{       \
return [super autorelease];     \
}


#pragma mark -
#pragma mark Lesser Singleton

/* A lesser singleton has a shared instance, but can also be instantiated on its own.
 *
 * For a lesser singleton, you still use SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(),
 * but use SYNTHESIZE_LESSER_SINGLETON_FOR_CLASS() in the implementation file.
 * You must specify which creation methods are to initialize the shared instance
 * (besides "sharedInstance") via CALL_LESSER_SINGLETON_INIT_METHOD()
 *
 * Example:
 *
 * MyClass.h:
 * ========================================
 *      #import "SynthesizeSingleton.h"
 *
 *      @interface MyClass: SomeSuperclass
 *      {
 *              int value;
 *              ...
 *      }
 *      SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(MyClass);
 *
 *      + (void) initSharedInstanceWithValue:(int) value;
 *
 * - (id) initWithValue:(int) value;
 *
 *      @end
 * ========================================
 *
 *
 *      MyClass.m:
 * ========================================
 *      #import "MyClass.h"
 *
 *      // This line is optional. Use it if you've enabled GCC_WARN_UNDECLARED_SELECTOR
 *      SYNTHESIZE_SINGLETON_FOR_CLASS_PROTOTYPE(MyClass);
 *
 *      @implementation MyClass
 *
 *      SYNTHESIZE_LESSER_SINGLETON_FOR_CLASS(MyClass);
 *
 *      + (void) initSharedInstanceWithValue:(int) value
 *      {
 *              CALL_LESSER_SINGLETON_INIT_METHOD(MyClass, initWithValue:value);
 *      }
 *
 *      ...
 *
 *      @end
 * ========================================
 *
 *
 * Note: CALL_LESSER_SINGLETON_INIT_METHOD() will not work if your
 * init call contains commas. If you need commas (such as for varargs),
 * or other more complex initialization, use the PRE and POST macros:
 *
 *      + (void) initSharedInstanceComplex
 *      {
 *              CALL_LESSER_SINGLETON_INIT_METHOD_PRE(MyClass);
 *
 *              int firstNumber = [self getFirstNumberSomehow];
 *              _sharedInstance = [[self alloc] initWithValues:firstNumber, 2, 3, 4, -1];
 *
 *              CALL_LESSER_SINGLETON_INIT_METHOD_POST(MyClass);
 *      }
 */

#define SYNTHESIZE_LESSER_SINGLETON_FOR_CLASS(SS_CLASSNAME)     \
\
static volatile SS_CLASSNAME* _##SS_CLASSNAME##_sharedInstance = nil;   \
\
+ (SS_CLASSNAME*) sharedInstanceNoSynch \
{       \
return (SS_CLASSNAME*) _##SS_CLASSNAME##_sharedInstance;        \
}       \
\
+ (SS_CLASSNAME*) sharedInstanceSynch   \
{       \
@synchronized(self)     \
{       \
if(nil == _##SS_CLASSNAME##_sharedInstance)     \
{       \
_##SS_CLASSNAME##_sharedInstance = [[self alloc] init]; \
if(_##SS_CLASSNAME##_sharedInstance)    \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceNoSynch));  \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
}       \
}       \
}       \
return (SS_CLASSNAME*) _##SS_CLASSNAME##_sharedInstance;        \
}       \
\
+ (volatile SS_CLASSNAME*)sharedInstance        \
{       \
return (volatile SS_CLASSNAME*) [self sharedInstanceSynch]; \
}       \
\
+ (void)purgeSharedInstance     \
{       \
@synchronized(self)     \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceSynch));    \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
[_##SS_CLASSNAME##_sharedInstance release];     \
_##SS_CLASSNAME##_sharedInstance = nil; \
}       \
}


#define CALL_LESSER_SINGLETON_INIT_METHOD_PRE(SS_CLASSNAME) \
@synchronized(self)     \
{       \
if(nil == _##SS_CLASSNAME##_sharedInstance)     \
{


#define CALL_LESSER_SINGLETON_INIT_METHOD_POST(SS_CLASSNAME) \
if(_##SS_CLASSNAME##_sharedInstance)    \
{       \
Method newSharedInstanceMethod = class_getClassMethod(self, @selector(sharedInstanceNoSynch));  \
method_setImplementation(class_getClassMethod(self, @selector(sharedInstance)), method_getImplementation(newSharedInstanceMethod));     \
}       \
}       \
}


#define CALL_LESSER_SINGLETON_INIT_METHOD(SS_CLASSNAME,__INIT_CALL__) \
CALL_LESSER_SINGLETON_INIT_METHOD_PRE(SS_CLASSNAME); \
_##SS_CLASSNAME##_sharedInstance = [[self alloc] __INIT_CALL__];        \
CALL_LESSER_SINGLETON_INIT_METHOD_POST(SS_CLASSNAME)

#endif /* SYNTHESIZE_SINGLETON_FOR_CLASS */

使用時也非常方便,該頭文件也已給出使用方法,在這裏我在說一下,供那些E文不好的同學使用。

使用這種方式首先把該頭文件加到我們的項目中,然後直接使用就可以了:

Singleton.h

#import <Foundation/Foundation.h>
#import "SynthesizeSingleton.h"

@interface Singleton : NSObject

SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(Singleton);

//定義該類的屬性,方法等

@end
Singleton.m

@implementation Singleton

SYNTHESIZE_SINGLETON_FOR_CLASS(Singleton);

//屬性方法的實現

@end

如此一來在使用時,通過[Singleton sharedInstance]就可以獲得該類的單例對象了。 這種方法由於有了這個頭文件的支持,所以使得使用單例方便多了,而且也避免了多線程的問題。


第三種

這是最後一種也是我最推薦的一種。iOS在4.0以後推出了blockGCD,這兩個特性給iOS開發帶來的很大的便利,也使開發變得更加趣味話。那麼如何通過GCD+block實現單例模式呢,這主要歸功於dispatch_once(dispatch_once_t *predicate, ^(void)block)這個GCD的函數,他有兩個參數第一參數是一個指向dispatch_once_t類型結構體的指針,用來測試block是否執行完成,該指針所指向的結構體必須是全局的或者靜態的,第二個參數是一個返回值與參數均爲空的block,在block體中進行對象的初始化即可。dispatch_once在程序的生命週期中保證只會被調用一次,所以在多線程中也不會有問題。 該種方法使用方法:

+ (Singleton *)sharedInstance
{
    static Singleton *instance = nil;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[Singleton alloc]init];
    });

    return instance;
}

使用該種方法只需要這簡單的幾句代碼就可以實現單例了。使用起來非常方便,但是這種創建單例的方法也不是完美的,它並不能阻止人們通過alloc方法來實例化一個對象,所以這並不是嚴格意義上的單例模式,但是一般程序都是我們自己寫,我們自己記得就好了,這也沒什麼可擔心的,從這一點上來說第二種方法又是比較好的,具體使用的時候呢,根據實際情況來吧,各取所需就好了。

發佈了69 篇原創文章 · 獲贊 8 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章