文章目錄
- 單例模式
- 定義
- 特點
- 使用原因
- 缺點
- 模式介紹
- 懶漢模式
- 同步鎖實現
- dispatch_once
- 餓漢模式
- 實現
- 總結
- 懶漢模式
- 優點
- 缺點
- 餓漢模式
- 優點
- 缺點
單例模式
定義
單例模式,簡單的說就是一個類始終只對應同一個對象,每次獲取這個類的對象獲得的都是同一個實例
如果一個類始終只能創建一個實例,那么這個類被稱為單例類。單例類只有一個全局的接口來訪問這個實例。當第一次載入的時候,他通常使用延遲加載的方創建唯一單例。在程序中一個單例只初始化一次,為了保證在使用中始終存在,單例類的存儲是在存儲器的全局區域,在編譯時分配內存,只要程序在運行單例就始終存在,占用內存。在APP結束運行后釋放這部分內存。但是在系統方法中,存在未知的自動釋放池,如果對一個對象進行自動釋放的話,可能進入未知的釋放池,出現內存問題。即為單例模式不能自動釋放的原因
特點
- 單例類是一個類,這個類創造出的對象是單例對象
- 單例對象使用類方法創建
- 單例一旦被創建出來,直到程序結束運行才會釋放
- 單例不用我們來管理內存,內存會隨著程序關閉而被釋放
使用原因
節省內存,防止一個實例被重復創建從而占用內存空間。
缺點
- 全局狀態:可能導致全局狀態的存在,使得程序難以調試,一個地方修改容易導致很多地方發生變化
- 難以拓展:單例模式的實例是固定的,難以拓展以支持多個實例,必須修改代碼,使單例類丟失單例的特性
- 隱藏依賴關系:可能會隱藏單例類的依賴關系,代碼更加耦合。
模式介紹
懶漢模式
是一種常見的單例設計模式,其主要特點是在需要時在創建單例對象的實現,通過延遲對象的初始化來節省資源和提高性能。適用于訪問量較小的情況,使用時間來換取空間
同步鎖實現
#import "Person.h"@implementation Person
static id instance = nil;
+ (instancetype)sharedInstance {@synchronized (self) {if (!instance) {instance = [[super allocWithZone:NULL] init];}}return instance;
}
@end
這一段代碼提供了一個全局訪問點sharedInstace類方法提供給外部訪問這個單例,在方法內部通過添加了一個同步鎖,限制了同一時間只有一個線程訪問臨界區的代碼。但是這里如果instance已經存在是沒有必要進入鎖的,所以可以再加個判斷如下:
#import "Person.h"@implementation Person
static id instance = nil;
+ (instancetype)sharedInstance {if (!instance) {@synchronized (self) {if (!instance) {instance = [[super allocWithZone:NULL] init];}}}return instance;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone {if (!instance) {@synchronized (self) {if (!instance) {instance = [super allocWithZone:zone];}}}return instance;
}- (id)copyWithZone:(NSZone *)zone {return self;
}- (id)mutableCopyWithZone:(NSZone *)zone {return self;
}
@end
對于allocWithZone的參數,傳什么其實都無所謂,分配的內存區域不會改變
內層if的作用:假設有兩個線程A和B同時調用了sharedInstance,線程A先一步獲取鎖,進入代碼塊,此時線程B等待鎖釋放,當線程A初始化instance后釋放鎖,B獲得鎖,如果沒有內層if判斷,會再次創建實例,破壞了單例的唯一性
dispatch_once
通過一個靜態的dispatch_once變量來跟蹤代碼的執行狀態,第一次調用時原子操作發現標記狀態為未執行代碼塊,并原子性的將標記設為已執行,后續監測到標記為已執行,就會跳過該代碼塊。非常高效、沒有阻塞開銷
#import "Person.h"@implementation Person
static id instance = nil;
+ (instancetype)sharedInstance {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [[super allocWithZone:NULL] init];});return instance;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [super allocWithZone:zone];});return instance;
}- (id)copyWithZone:(NSZone *)zone {return self;
}- (id)mutableCopyWithZone:(NSZone *)zone {return self;
}
@end
我們打印出不同情況下的對象地址如下:
餓漢模式
在類加載過程就完成這個單例的創建和初始化
實現
#import "Person.h"@implementation Person
static id instance = nil;+ (void)load {instance = [[super allocWithZone:NULL] init];
}+ (instancetype)sharedInstance {return instance;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone {return instance;
}- (instancetype)init {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{NSLog(@"init一次");});return self;
}- (id)copyWithZone:(NSZone *)zone {return self;
}- (id)mutableCopyWithZone:(NSZone *)zone {return self;
}@end
總結
懶漢模式
優點
- 延遲加載,節省資源,提高性能。
- 避免在程序啟動時就創造不必要的對象,造成額外開銷
- 可以通過加鎖實現線程安全
缺點
- 為實現線程安全使用鎖機制,可能會引起一些性能開銷
餓漢模式
優點
- 實現簡單,不需要考慮線程安全問題,實例在類加載時就已經創建了,所以不用考慮創建多個實例的風險
缺點
- 程序啟動時就加載創建實例,可能會浪費資源,引起性能問題
- 如果單例對象的創建需要建立在某些外部前提下,那么不適合餓漢模式