iOS 實用方法創建單利+宏定義創建單利

有兩種方法來創建單例,下面分別介紹

1、GCD方式創建單例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static id _instance;   
 
+ (instancetype)allocWithZone:(struct _NSZone *)zone  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [super allocWithZone:zone];   
    });   
    return _instance;   
}   
 
+ (instancetype)sharedInstance  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [[self alloc] init];   
    });   
    return _instance;   
}   
 
- (id)copyWithZone:(NSZone *)zone   
{   
    return _instance;   
}  
 
- (id)mutableCopyWithZone:(NSZone *)zone {   
    return _instance;   
}

2、互斥鎖方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{    @synchronized(self) {        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }    return _instance;
}
+ (instancetype)sharedInstance
{    @synchronized(self) {        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
    }    return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{    return _instance;
}

上面兩種方式都可以創建單例,而且保證了用戶不管是通過shareInstance方法,還是alloc、copy方法得到的實例都是一樣的。

上面代碼的關鍵之處就在於如何在多線程情況下保證創建的單例還是同一個。

我們先看看在GCD情況下,如果不使用dispatch_once和同步鎖創建單例會出現什麼問題,去掉兩者後創建單例的代碼如下

1
2
3
4
5
6
+ (instancetype)sharedInstance  
{   
 if (_instance == nil) {
     _instance = [[self alloc] init];
  }
}

假設此時有兩條線程:線程1和線程2,都在調用shareInstance方法來創建單例,那麼線程1運行到if (_instance == nil)出發現_instance = nil,那麼就會初始化一個_instance,假設此時線程2也運行到if的判斷處了,此時線程1還沒有創建完成實例_instance,所以此時_instance = nil還是成立的,那麼線程2又會創建一個_instace。

此時就創建了兩個實例對象,導致問題。

解決辦法1、使用dispatch_once

dispatch_once保證程序在運行過程中只會被運行一次,那麼假設此時線程1先執行shareInstance方法,創建了一個實例對象,線程2就不會再去執行dispatch_once的代碼了。從而保證了只會創建一個實例對象。

解決辦法2、使用互斥鎖

假設此時線程1在執行shareInstance方法,那麼synchronize大括號內創建單例的代碼,如下所示:

1
2
3
if (_instance == nil) {
            _instance = [[self alloc] init];
        }

就會被當做一個任務被加上了一把鎖。此時假設線程2也想執行shareInstance方法創建單例,但是看到了線程1加的互斥鎖,就會進入睡眠模式。等到線程1執行完畢,纔會被喚醒,然後去執行上面所示的創建單例的代碼,但是此時_instance !=nil,所以不會再創建新的實例對象了。從而保證只會創建一個實例對象。

但是互斥鎖會影響性能,所以最好還是使用GCD方式創建單例。


宏創建單例

如果我們需要在程序中創建多個單例,那麼需要在每個類中都寫上一次上述代碼,非常繁瑣。

我們可以使用宏來封裝單例的創建,這樣任何類需要創建單例,只需要一行代碼就搞定了。

實現代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Singleton.h文件
==================================
 
#define SingletonH(name) + (instancetype)shared##name;
 
#define SingletonM(name) \
static id _instance; \
 \
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        _instance = [super allocWithZone:zone]; \
    }); \
    return _instance; \
} \
 \
+ (instancetype)shared##name \
{ \
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{ \
        _instance = [[self alloc] init]; \
    }); \
    return _instance; \
} \
 \
- (id)copyWithZone:(NSZone *)zone \
{ \
    return _instance; \
}\
\
- (id)mutableCopyWithZone:(NSZone *)zone { \
    return _instance; \
}

如何調用

假設我們要在類viewcontroller中使用,調用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
viewcontroller.h文件
===========================
 
#import <uikit uikit.h="">
#import "Singleton.h"
 
@interface ViewController : UIViewController
SingletonH(viewController)
@end
 
 
 
viewcontroller.m文件
===========================
 
@interface ViewController ()
 
@end
 
@implementation ViewController
 
SingletonM(ViewController)
 
- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSLog(@"%@ %@ %@ %@", [ViewController sharedViewController],[ViewController sharedViewController], [[ViewController alloc] init],[[ViewController alloc] init]);
 
}
 
@end</uikit>

輸出結果

1
 <viewcontroller: 0x7f897061bc00> <viewcontroller: 0x7f897061bc00> <viewcontroller: 0x7f897061bc00> <viewcontroller: 0x7f897061bc00></viewcontroller: 0x7f897061bc00></viewcontroller: 0x7f897061bc00></viewcontroller: 0x7f897061bc00></viewcontroller: 0x7f897061bc00>

可以看到四個對象的內存地址完全一樣,說明是同一個對象

發佈了135 篇原創文章 · 獲贊 31 · 訪問量 32萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章