文章目錄
- 前言
- 概念
- 優缺點
- 優點
- 缺點
- 兩種使用模式
- 懶漢模式
- 實現代碼
- 運行結果
- 餓漢模式
- 實現代碼
- 運行結果
- 在自定義類方法時的幾種常見寫法
- 總結
前言
在之前我們已經學習過單例模式的有關內容,但是只是最簡單的單例,無法勝任多線程或者稍微多一點的情況便無法確定單例的唯一性,于是更深度的學習了單例模式
概念
單例模式的定義:一個類有且只有一個實例,并且自行實例化向整個系統提供。
即他用自己內部方法進行創立的唯一對象實例,并且可以被全局訪問
優缺點
優點
-
全局訪問
單例模式就像一個全局變量,可以通過統一的入口來獲取,并可以更方便的共享一些全局的數據與資源
-
節省資源
只創建一次實例,避免了頻繁的new/alloc
-
控制實例化過程
通過私有函數和靜態方法控制對象的唯一性,保持了數據的唯一性
缺點
-
隱藏依賴,增加耦合
很多地方都直接訪問單例,形成一種“隱形依賴”,在更改或替換單例時導致牽一發而動全身
-
不利于擴展與測試
通常通過靜態方法提供實例,全局固定,難以繼承或替換
-
多線程安全問題
在并發環境下,如果單例初始化沒有處理好線程安全(比如加鎖或使用 dispatch_once),可能會創建出多個實例,違背單例的初衷,尤其在懶漢式單例實現中,這一點必須特別小心。
兩種使用模式
一般來說,創建一個單例之后要保證唯一實例的話要分別改寫四種方法,即:用alloc init創建;通過類方法創建;通過copy創建;通過mutableCopy創建
而在改寫這四種方法時按照創建時間主要分為兩種,即懶漢模式和餓漢模式
懶漢模式
即在我們需要用到這個單例的時候,我們才開始創建這個唯一的實例,通過延遲對象的初始化來節省資源和提高性能,這種也是比較常用的創建單例模式的方式:
實現代碼
+ (instancetype)sharedInstance {static Singletion *instance = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [[super allocWithZone: NULL] init];});return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {return self;
}
#import <Foundation/Foundation.h>
#import "Singletion.h"int main(int argc, const char * argv[]) {@autoreleasepool {Singletion *s1 = [Singletion sharedInstance];Singletion *s2 = [Singletion sharedInstance];Singletion *s3 = [s1 copy];Singletion *s4 = [s1 mutableCopy];NSLog(@"%d", s1 == s2);NSLog(@"%d", s1 == s3);NSLog(@"%d", s2 == s3);NSLog(@"%d", s1 == s4);}return 0;
}
這里涉及到了一個新的東西,即dispatch_once,經學長博客學習,發現其主要是按照onceToken的值來進行代碼執行的
- onceToken = 0時,線程執行里面block中的代碼
- onceToken = -1時,線程跳過block里的代碼不執行
- onceToken = 其他值時,線程即會被阻塞,等待onceToken的值改變
當線程調用mySingleton方法時,此時 onceToken = 0,調用 block 中的代碼,此時 onceToken =其他值。
當其他線程再調用 mySingleton 方法時,onceToken為其他值,線程阻塞。當 block 線程執行完 block之后,onceToken = -1,其他線程不再阻塞,跳過 block。下次再調用這個初始化方法時, block 已經為-1,直接跳過 block
運行結果
餓漢模式
餓漢模式指的是我們在一開始加載時就直接創建這個單例對象的實例,使用時再把這個對象拿出來,用到的這種方式不是特別常用,因為性能不如上面的懶漢模式
餓漢有個優點就是,因為這個實例在加載時就已經創建完成,所以其不存在多線程創建的問題,因而一般來說也不需要用dispatch_once或者加鎖方法,當然用了也行,不過好像是有點多余的寫法
實現代碼
#import "Singletion.h"@implementation Singletionstatic Singletion* instance = nil;+ (void)load {instance = [[super allocWithZone: NULL] init];//NSLog(@"Singleton");
}
+ (instancetype)sharedInstance {return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {if (!instance) {instance = [super allocWithZone: zone];}return instance;
}
- (id)copyWithZone:(NSZone *)zone {return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {return self;
}
@end
int main(int argc, const char * argv[]) {@autoreleasepool {Singletion *s1 = [Singletion sharedInstance];Singletion *s2 = [Singletion sharedInstance];Singletion *s3 = [s1 copy];Singletion *s4 = [s1 mutableCopy];NSLog(@"%d", s1 == s2);NSLog(@"%d", s1 == s3);NSLog(@"%d", s2 == s3);NSLog(@"%d", s1 == s4);}return 0;
}
運行結果
在自定義類方法時的幾種常見寫法
首先我們知道餓漢一般是不需要擔心其線程安全問題的,所以一般只考慮懶漢模式的幾種寫法,主要有兩種
在懶漢模式中一般有兩種寫法,分別是GCD和加鎖的寫法,GCD的寫法是現在寫法更推薦的,因為其性能極快且第一次使用后后續基本無開銷,而使用加互斥鎖@synchronized的方式性能較慢,且每次使用時都有鎖的開銷所以不常用
GCD的寫法在上面已經給出,下面我給出使用加鎖方式的代碼:
static Singletion* instance = nil;
+ (id)sharedInstance {if (instance == nil) {@synchronized (self) {if (instance == nil) {instance = [[super allocWithZone: NULL] init];}}}return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {if (instance == nil) {@synchronized (self) {if (!instance) {instance = [[super allocWithZone: NULL] init];}}}return instance;
}
- (id)copyWithZone:(NSZone *)zone {return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {return self;
}
總結
總而言之,懶漢模式一般用于需要延遲加載實例的情況,可以節省資源,提高性能,但是需要考慮線程安全的問題;餓漢模式適用于需要簡單實現和線程安全的情況,但是不支持延遲加載,在程序開始時便加載完成了