1. 認識單例模式
首先讓我們先看下關于單例模式的定義
(來自于《設計模式》(Addison-Wesley,1994))
一個類有且僅有一個實例,并且自行實例化向整個系統提供。
如果說每一個人都是一個類,那么從他出生開始,他就是生活中的唯一實例,每當有人要拜訪或者聯系你的時候,無論別人認識你的時候你是什么狀態,他所能聯系到的都是現在的你。你本身的狀態會在其他地方發生改變,即當你狀態改變后,后續所有找到你的,都是看到狀態改變后的你。那么,我們就可以認為每一個人都處于單例模式。
在 iOS 中,系統默認就有許多支持單例方法的地方,例如通知中心、應用管理、本地保存等,但是一般都屬于非嚴格的單例模式,也就是你可以使用
[UIApplication sharedApplication]
來獲取單例對象,也可以通過[UIApplication new]
生成一個新的對象,而如果按照準確的單例定義來說,依舊以UIApplication
類為例,[UIApplication new]
與[UIApplication sharedApplication]
應該在任何時候返回的對象都是相同的。也就是說無論你怎么操作,只會存在一個實例,不會創建其他的副本內容。
2. 單例模式的使用
單例模式需要實現一個公共訪問的類方法,一般命名為 shared + 類名。在該方法的具體實現方案,是推薦通過dispatch_once 來實現類的實例化。
可以直接通過重寫父類的方法,把分配內存的方法變成只執行一次。從根本上實現了單例。
如果按照嚴格的單例寫法的話,單例模式一共需要重寫五個方法 :
+ (instancetype)allocWithZone:(struct _NSZone *)zone
+ (id)copyWithZone:(struct _NSZone *)zone
+ (id)mutableCopyWithZone:(struct _NSZone *)zone
- (id)copyWithZone:(NSZone *)zone
- (id)mutableCopyWithZone:(NSZone *)zone
或者,可以把這些方法全都通過attribute((unavailable (invalid ))); 方式禁止,以保證一定使用shhared的單例方法。
SingleView.h#import <Foundation/Foundation.h>@interface SingleView : NSObject#pragma mark- method
+ (SingleView *)sharedSingleView;
+ (id)alloc __attribute__((unavailable("invalid, use sharedSingleView instead")));
+ (id)new __attribute__((unavailable("invalid, use sharedSingleView instead")));
- (id)copy __attribute__((unavailable("invalid, use sharedSingleView instead")));
- (id)mutableCopy __attribute__((unavailable("invalid, use sharedSingleView instead")));@end
如果只是在自己的項目中使用的話,那么直接實現一個 shared 的類方法基本都能滿足需求,由于是自己的內容,代碼是受控的,實現并調用即可。而如果是對外的庫的話(靜態庫、動態庫),這需要根據具體的業務內容考慮是否需要按照嚴格的單例寫法來實現了。
懶漢
懶漢式創建單例模式的意思其實就是指我們在創建的時間是我們需要用到這個單例的時候,我們才開始創建這個唯一實例,這種創建模式有助于提高性能,以及節省資源的效果。簡單來說,就是我們平時日常生活中的deadline,只要在達到deadline的時候我們才去提交工作,這和他的名字也很相似,懶漢式延遲創建。
dispatch_once
//SingleView.h#import <Foundation/Foundation.h>@interface SingleView : NSObject
+ (SingleView *)sharedSingleView;
@end
//SingleView.m#import "SingleView.h"@implementation SingleView#pragma mark- public method
+ (SingleView *)sharedSingleView {static SingleView *single = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{single = [[super allocWithZone:NULL] init];});return single;
}+ (instancetype)allocWithZone:(struct _NSZone *)zone {return [SingleView shareSingleView];
}+ (id)copyWithZone:(struct _NSZone *)zone {return [SingleView shareSingleView];
}+ (id)mutableCopyWithZone:(struct _NSZone *)zone {return [SingleView shareSingleView];
}- (id)copyWithZone:(NSZone *)zone{return [SingleView shareSingleView];
}- (id)mutableCopyWithZone:(NSZone *)zone{return [SingleView shareSingleView];
}
@end
同步鎖實現
static Singleton* 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;
}
@end
餓漢
餓漢式創建單例則是在你的類加載的時候立刻就開始加載一個單例的一種單例模式,這種模式我們需要把我們的加載單例的代碼寫在類第一次加載的位置。
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;
}
輸出結果如下證明單例代碼有效:
因為對block部分對不了解,引用學長博客中的一段話:
ispatch_once 主要是根據 onceToken 的值來決定怎么去執行代碼。
1.當 onceToken = 0 時,線程執行 dispatch_once 的 block 中代碼;
2.當 onceToken = -1 時,線程跳過 dispatch_once 的 block 中代碼不執行;
3.當 onceToken 為其他值時,線程被阻塞,等待 onceToken 值改變。
當線程調用mySingleton方法時,此時 onceToken = 0,調用 block 中的代碼,此時 onceToken =其他值。
當其他線程再調用 mySingleton 方法時,onceToken為其他值,線程阻塞。當 block 線程執行完 block之后,onceToken = -1,其他線程不再阻塞,跳過 block。下次再調用mySingleton方法 時, block 已經為-1,直接跳過 block。轉載自[【iOS】—— 單例模式]
(https://blog.csdn.net/m0_73974920/article/details/132909916?spm=1001.2014.3001.5502)
這里又要注意一下要調用父類的【super allocWithZone:null】這個方法,因為我們這里已經重寫這個類的方法了,不然會出現一個循環調用的問題。同時為了防止copy和mutablecopy兩個方法的出現崩潰的問題。
3.總結
單例模式是一種非常常見、使用頻率也很高的一種設計模式。單例能夠在許多場合使用,有時候也可以用來保存數據。
它(嚴格單例)存在著以下特點:
節省內存;
在程序的運行周期中保存數據;
只能在自己的類中實現實例;
程序的運行周期中,內存不會被釋放;
類中有且僅有一個實例;
懶漢模式:
· 優點:
延遲加載:懶漢模式只有在第一次訪問單例實例時才會進行初始化,可以節省資源,提高性能,因為實例只有在需要時才會被創建。
節省內存:如果單例對象很大或者初始化過程開銷較大,懶漢模式可以避免在程序啟動時就創建不必要的對象。
線程安全性:可以通過加鎖機制(如雙重檢查鎖定)來實現線程安全。
· 缺點:
線程安全性開銷:懶漢模式在實現線程安全時可能需要額外的同步機制,這會引入一些性能開銷。
復雜性增加:實現線程安全的懶漢模式可能需要編寫復雜的代碼,容易引入錯誤。
缺點:
線程安全性開銷:懶漢模式在實現線程安全時可能需要額外的同步機制,這會引入一些性能開銷。
復雜性增加:實現線程安全的懶漢模式可能需要編寫復雜的代碼,容易引入錯誤。
餓漢模式:
優點:
簡單:餓漢模式實現簡單,不需要考慮線程安全問題,因為實例在類加載時就已經創建。
線程安全性:由于實例在類加載時創建,不會存在多個實例的風險,因此線程安全。
缺點:
無法實現延遲加載:餓漢模式在程序啟動時就創建實例,無法實現延遲加載,可能會浪費資源,特別是當實例很大或初始化開銷較大時。
可能引起性能問題:如果單例類的實例在程序啟動時沒有被使用,那么創建實例的開銷可能是不必要的。
不適用于某些情況:如果單例對象的創建依賴于某些外部因素,而這些因素在程序啟動時無法確定,那么餓漢模式可能不適用。
總的來說,懶漢模式適用于需要延遲加載實例的情況,可以節省資源和提高性能,但需要考慮線程安全性。餓漢模式適用于需要簡單實現和線程安全性的情況,但不支持延遲加載。選擇哪種模式應根據具體需求和性能考慮來決定。