iOS中單例使用以及優(yōu)缺點

一、單例介紹

單例模式:單例模式,屬于創(chuàng)建類型的一種常用的軟件設計模式。通過單例模式的方法創(chuàng)建的類在當前進程中只有一個實例。

為了我們能更好的理解單例模式,我列舉以下幾個cocoa框架中常用的單例:

1.UIApplication:應用程序。一個UIApplication對象就代表著一個應用程序,每個應用程序有且僅有一個UIApplication對象,開發(fā)中最常用的是使用它的openURL函數(shù)來跳轉到其他應用程序,通過 [UIApplication sharedApplication] 類方法可以獲得。

2.NSNotificationCenter:通知中心。iOS中的通知中心是一種消息廣播,采用觀察者模式和單例模式,一個應用有且僅有一個通知中心。通過 [NSNotificationCenter defaultCenter] 類方法可以獲得。

3.NSFileManager:文件管理器。它是iOS文件系統(tǒng)的接口,用來創(chuàng)建、修改、訪問文件。一個應用有且僅有一個文件管理器。通過 [NSFileManager defaultManager] 類方法可以獲得。

4.NSUserDefaults:用戶偏好設置。它主要用來存儲簡單的鍵值對數(shù)據(jù),數(shù)據(jù)持久化最簡單和基礎的一種方案。通過 [NSUserDefaults standardUserDefaults] 類方法可以獲得。

5.NSURLCache:URL緩存。通過將NSURLRequest對象映射到NSCachedURLResponse對象來實現(xiàn)對URL加載請求的響應的緩存。通過 [NSURLCache sharedURLCache] 類方法可以獲得。

1.1 單例模式的要點

  • 1.只能有一個實例;

  • 2.它必須自行創(chuàng)建這個實例;

  • 3.它必須自行向整個系統(tǒng)提供這個實例。

從具體實現(xiàn)角度來說,是以下三點:

  • 1.單例模式的類只提供私有的構造函數(shù);

  • 2.類定義中含有一個該類的靜態(tài)私有對象(實例);

  • 3.提供一個靜態(tài)的公有函數(shù)用于創(chuàng)建或獲取它本身的靜態(tài)私有對象(實例)。

1.2 單例模式的優(yōu)點

  • 1.實例控制:單例可以保證系統(tǒng)中該類有且僅有一個實例,確保所有對象都訪問這個唯一實例;

  • 2.靈活性:因為類控制了實例化的過程,所以類可以靈活更改實例化過程;

  • 3.節(jié)省開銷:因為只有一個實例,所以減少內(nèi)存開發(fā)和系統(tǒng)的性能開銷。

1.3 單例模式的缺點

  • 1.由于單例模式中沒有抽象層,可擴展性比較差。

  • 2.實例一旦被創(chuàng)造,對象指針保存在靜態(tài)區(qū),那么在堆區(qū)分配的空間只有在App結束后才會被釋放;

  • 3.單例類職責過重,在一定程度上違背了“單一職責原則”。

  • 4.濫用單例會帶來一些負面問題,比如,單例會隱性地讓毫不相關的類產(chǎn)生耦合等問題。

二、單例的實現(xiàn)

單例的實現(xiàn)重點就是防止在外部調(diào)用的時候出現(xiàn)多個不同的實例,也就是說要從創(chuàng)建的方式入手禁止出現(xiàn)多個不同的實例。

主要做到以下幾點:

1.防止調(diào)用 [[A alloc] init] 引起錯誤

2.防止調(diào)用 new 引起錯誤

3.防止調(diào)用 copy 引起錯誤

4.防止調(diào)用 mutableCopy 引起錯誤

2.1 典型的單例寫法

static id sharedMyManager;
 +(id)shareThemeManager{
     if(sharedThemeManager == nil){
              shareMyManager = [[self alloc]init];
     }
   return sharedMyManager;
 }

缺點:無法保證多線程情況下只創(chuàng)建一個對象。適用于只有單線程。

2.2 加鎖的寫法

 static Singleton *_sharedSingleton = nil;

+(instancetype)sharedSingleton {
    @synchronized(self){   //加鎖,保證多線程下也只能有一個線程進入
        if (! _sharedSingleton) {
            _sharedSingleton = [[self alloc] init]; 
        } 
    } 
    return _sharedSingleton;
 }

2.3 GCD寫法【常用】

2.3.1 重寫父類方法
  • static Singleton *_sharedSingleton = nil;
    
    + (instancetype)sharedSingleton
      {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 不能再使用 alloc 方法
            // 因為已經(jīng)重寫了 allocWithZone 方法,所以這里要調(diào)用父類的分配空間的方法
            _sharedSingleton = [[super allocWithZone:NULL] init];
        });
        return _sharedSingleton;
      }
    
    // ②、防止 [[A alloc] init] 和 new 引起的錯誤。因為 [[A alloc] init] 和 new 實際是一樣的工作原理,都是執(zhí)行了下面方法
    
    + (instancetype)allocWithZone:(struct _NSZone *)zone
      {
        return [Singleton sharedSingleton];
      }
    
    // ③、NSCopying 防止 copy 引起的錯誤。當你的單例類不遵循 NSCopying 協(xié)議,外部調(diào)用本身就會出錯.
    
    - (id)copyWithZone:(nullable NSZone *)zone
      {
        return [Singleton sharedSingleton];
      }
    
    // ④、防止 mutableCopy 引起的錯誤,當你的單例類不遵循 NSMutableCopying 協(xié)議,外部調(diào)用本身就會出錯.
    
    - (id)mutableCopyWithZone:(nullable NSZone *)zone 
      {
        return [Singleton sharedSingleton];
      }
      dispatch_once 主要是根據(jù) onceToken 的值來決定怎么去執(zhí)行代碼。
    

1.當 onceToken = 0 時,線程執(zhí)行 dispatch_once 的 block 中代碼;

2.當 onceToken = -1 時,線程跳過 dispatch_once 的 block 中代碼不執(zhí)行;

3.當 onceToken 為其他值時,線程被阻塞,等待 onceToken 值改變。

當線程調(diào)用 shareInstance,此時 onceToken = 0,調(diào)用 block 中的代碼,此時 onceToken = 其他值。當其他線程再調(diào)用 shareInstance 方法時,onceToken為其他值,線程阻塞。當 block 線程執(zhí)行完 block 之后,onceToken = -1,其他線程不再阻塞,跳過 block。下次在調(diào)用 shareInstance 時, block 已經(jīng)為 -1,直接跳過 block。

2.3.2 禁止外部調(diào)用

這種寫法還蠻簡單好用的~

.h 文件

  • - (instancetype)init NS_UNAVAILABLE;
    
    + (instancetype)new NS_UNAVAILABLE;
    
    - (id)copy NS_UNAVAILABLE;
    - (id)mutableCopy NS_UNAVAILABLE;
    

.m 文件

  • static Singleton *_sharedSingleton = nil;
    
    + (instancetype)sharedSingleton
      {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
              _sharedSingleton = [[self alloc] init];  // 要使用 self 來調(diào)用
        });
        return _sharedSingleton;
      }
    

    當運行 init 或者 new 時,會報錯 'init' is unavailable 或者 'new' is unavailable。

2.3.3 宏定義寫法

寫成宏是比較方便也比較常見的一種:

#define MY_SINGLETON_DEF(_type_) + (_type_ *)sharedInstance; \
+(instancetype) alloc __attribute__((unavailable("call sharedInstance instead"))); \
+(instancetype) new __attribute__((unavailable("call sharedInstance instead"))); \
-(instancetype) copy __attribute__((unavailable("call sharedInstance instead"))); \
-(instancetype) mutableCopy __attribute__((unavailable("call sharedInstance instead"))); \
#define MYY_SINGLETON_IMP(_type_) + (_type_ *)sharedInstance{ \
static _type_ * sharedInstance = nil; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
sharedInstance = [[super alloc] init]; \
}); \
return sharedInstance; \
}

用法也很簡單:

//引用
@interface MYSingleton : NSObject
MY_SINGLETON_DEF(MYSingleton);
@end
//實現(xiàn)
@implementation MYSingleton
MY_SINGLETON_IMP(MYSingleton);
@end

2.4 免鎖寫法

static Singleton *_sharedSingleton = nil;

+ (instancetype)sharedSingleton {
  static BOOL initialized = NO;
  if (initialized == NO){
      initialized = YES;
      _sharedSingleton = [[self alloc] init];
  }
  return _sharedSingleton;
   }

1,2,4三種寫法還需將 init new 重寫或禁用才算完整哦~

三、單例的濫用

上面關于單例的部分,寫的太好了,所以我把全文搬運過來了

原文地址:iOS 單例模式詳解/避免濫用單例

有興趣的可以去看看

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容