書寫一個完整的單例

前言

什麼是單例?

一個類只允許有一個實例,在整個程序中需要多次使用,共享同一份資源的時候,就可以創建單例,一般封裝成工具類使用,蘋果封裝成單例常用的有 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] 類方法創建出來單例singOnesingTwo 的內純地址是一樣的,說明單例創建對了,但是看到newalloc init 創建的singTheresingFour 的內存地址不一樣,同時也與singOnesingTwo的地址不一樣.

下面怎麼聊聊怎麼解決這個問題

百度一下還真有這類的帖子和博客,我們主要的問題就是保證單例的唯一性,避免不小心用實例方法創建單例,所有應該保證alloc initnewcopy 方法創建的單例的的唯一性。

在創建對象的時候主要分這麼兩步 alloc (申請內存)init(初始化)

  1. 我們在第一步alloc的會後就要對其進行攔截。當我們去調用alloc的時候,OC內部會調用allocWithZone這個方法去申請內存,我們去覆寫這個方法,然後在這個方法中調用之前的類方法,返單例對象,這樣就能達到我們的目的了。
  2. 拷貝對象也是一樣的,覆寫copyWithZone方法,然後在方法中去調用類方法,返回單例對象。(在覆寫copyWithZone方法之前別忘記了簽署NSCopying協議)

  3. 參考:書寫一個嚴謹的單例

下面修改一下 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.mSingleton類方法的類方法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
)

聲明

感謝以上兩位博主的文章,借鑑做了一份單例的筆記,記錄開發中的問題並解決問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章