前言
什麼是單例?
一個類只允許有一個實例,在整個程序中需要多次使用,共享同一份資源的時候,就可以創建單例,一般封裝成工具類使用,蘋果封裝成單例常用的有 UIApplication
,NSUserDefaults
,NSNotificationCenter
,NSFIleManager
等等。
單例的實現
我以前的寫法 (不嚴謹的寫法)
// Singleton.h
@interface Singleton : NSObject
+ (instancetype)sharedInstance;
@end
// Singleton.m
static Singleton *_instance = nil;
@implementation Singleton
+(instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[Singleton alloc] init];
});
return _instance;
}
@end
這不算真正意義上的單例,不能保證全局的唯一性,因爲類方法和 [ [Class alloc] init ] 方法創建的單例內存地址可能不一樣,所有不嚴謹。
下面舉個栗子��
Singleton *singOne = [Singleton sharedInstance];
Singleton *singTwo = [Singleton sharedInstance];
Singleton *singThere = [Singleton new];
Singleton *singFour = [[Singleton alloc] init];
NSLog(@"singOne:-> %@",singOne);
NSLog(@"singTwo:-> %@",singTwo);
NSLog(@"singThere:-> %@",singThere);
NSLog(@"singFour:-> %@",singFour);
這時我看查看一下控制檯輸出的信息
2018-02-07 10:46:25.024988+0800 Markdown[2056:67507] singOne:-> <Singleton: 0x604000001a00>
2018-02-07 10:46:25.025222+0800 Markdown[2056:67507] singTwo:-> <Singleton: 0x604000001a00>
2018-02-07 10:46:25.025372+0800 Markdown[2056:67507] singThere:-> <Singleton: 0x6040000019a0>
2018-02-07 10:46:25.025646+0800 Markdown[2056:67507] singFour:-> <Singleton: 0x604000001a10>
結果很明顯, [Singleton sharedInstance]
類方法創建出來單例singOne
和 singTwo
的內純地址是一樣的,說明單例創建對了,但是看到new
和 alloc init
創建的singThere
和 singFour
的內存地址不一樣,同時也與singOne
和 singTwo
的地址不一樣.
下面怎麼聊聊怎麼解決這個問題
百度一下還真有這類的帖子和博客,我們主要的問題就是保證單例的唯一性,避免不小心用實例方法創建單例,所有應該保證alloc init
、new
、copy
方法創建的單例的的唯一性。
在創建對象的時候主要分這麼兩步 alloc (申請內存)init(初始化)
- 我們在第一步alloc的會後就要對其進行攔截。當我們去調用alloc的時候,OC內部會調用allocWithZone這個方法去申請內存,我們去覆寫這個方法,然後在這個方法中調用之前的類方法,返單例對象,這樣就能達到我們的目的了。
拷貝對象也是一樣的,覆寫copyWithZone方法,然後在方法中去調用類方法,返回單例對象。(在覆寫copyWithZone方法之前別忘記了簽署NSCopying協議)
下面修改一下 Singleton.m
參考:iOS-單例模式簡單使用
// Singleton.m
static Singleton *_instance = nil;
@implementation Singleton
+(instancetype)sharedInstance
{
if (_instance == nil) {
_instance = [[super alloc]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 _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
return _instance;
}
看一下控制檯輸出的結果:
2018-02-07 11:20:46.993693+0800 Markdown[2374:88765] singOne:-> <Singleton: 0x604000001990>
2018-02-07 11:20:46.993906+0800 Markdown[2374:88765] singTwo:-> <Singleton: 0x604000001990>
2018-02-07 11:20:46.994040+0800 Markdown[2374:88765] singThere:-> <Singleton: 0x604000001990>
2018-02-07 11:20:46.994146+0800 Markdown[2374:88765] singFour:-> <Singleton: 0x604000001990>
很明顯,改過 Singleton.m
後Singleton
類方法的類方法sharedInstance
和 實例方法 alloc init
的方法創建的單例內存地址一樣了,是不是有點小喜悅。
你以爲這就完事了嗎? NO、NO、NO
哪個單例裏面沒有幾個屬性,最少也得有一個吧,要不然我要這鐵(dan)棒(li)有何用。
這就加幾個屬性試試。
Singleton *singOne = [Singleton sharedInstance];
SingOne.array = @[@"1",@"2",@"3"];
Singleton *singTwo = [Singleton sharedInstance];
SingOne.array = @[@"4",@"5",@"6"];
Singleton *singThere = [Singleton new];
SingThere.array = @[@"7",@"8",@"9"];
Singleton *singFour = [[Singleton alloc] init];
SingFour.array = @[@"0",@"0",@"0"];
NSLog(@" singOne:-> %@ , %p , %@ ",singOne,singOne.array,singOne.array);
NSLog(@" singTwo:-> %@ , %p , %@ ",singTwo,singTwo.array,singTwo.array);
NSLog(@"singThere:-> %@ , %p , %@ ",singThere,singThere.array,singThere.array);
NSLog(@" singFour:-> %@ , %p , %@ ",singFour,singFour.array,singFour.array);
現在看看結果
array
屬性地址唯一,數組內容也唯一,滿足標準。
2018-02-07 11:48:39.843225+0800 Markdown[2804:110175] singOne:-> <Singleton: 0x60000003ec60> , 0x600000445250 , (
0,
0,
0
)
2018-02-07 11:48:39.843439+0800 Markdown[2804:110175] singTwo:-> <Singleton: 0x60000003ec60> , 0x600000445250 , (
0,
0,
0
)
2018-02-07 11:48:39.843589+0800 Markdown[2804:110175] singThere:-> <Singleton: 0x60000003ec60> , 0x600000445250 , (
0,
0,
0
)
2018-02-07 11:48:39.843713+0800 Markdown[2804:110175] singFour:-> <Singleton: 0x60000003ec60> , 0x600000445250 , (
0,
0,
0
)
聲明
感謝以上兩位博主的文章,借鑑做了一份單例的筆記,記錄開發中的問題並解決問題。