iOS单例模式

单例模式

单例模式(Singleton)作用

  • 可以保证的程序运行过程,一个类只有一个示例,并提供一个访问它的全局访问点方便外界访问
  • 控制了实例个数,并节约系统资源。

​ 单例写法有好几种,通常的写法是基于线程安全的写法,保证单例对象只会被创建一次,为单例对象创建一个静态实例,可以写成全局的,也可以在类方法中实现,并置为nil。

简要步骤
  • 1、先定义一个静态的instance.
  • 2、定一个sharedInstance的类方法.能够被全局调用的.此方法里需要考虑线程安全问题
  • 3、如果需要copy,需要遵守NSCopying协议,以及在copyWithZone中,直接返回self;

创建一个继承继承自NSObject的类AccountManager

AccountManager.h文件

1
2
3
4
5
6
7
8
9
10
11
12
//
// AccountManager.h
// Copyright © 2018年 hl.com.cn. All rights reserved.
//
#import <Foundation/Foundation.h>

@interface AccountManager : NSObject

/**快速创建单例的类方法*/
+ (instancetype)sharedInstance;

@end

AccountManager.m文件(简洁版,不考虑alloc,copy方式创建单例)

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//
// AccountManager.m
// Copyright © 2018年 hl.com.cn. All rights reserved.
//

#import "AccountManager.h"

@implementation AccountManager

//为单例对象创建的静态实例,因为对象的唯一性,必须是static类型
static AccountManager *_sharedInstance = nil;

#pragma mark---创建单例(普通,同步锁,GCD三种方式)
/**
* 最简单的单例(不建议使用)
* 缺点:没有考虑多线程并发问题(非线程安全)
*/
+ (instancetype)sharedInstance1{
if (!_sharedInstance) {
_sharedInstance = [[self alloc]init];
}
return _sharedInstance;
}


/**
*同步锁版本
*优点:线程安全
*缺点:每次运行代码前都要获取锁,效率较GCD慢
*/
+ (instancetype)sharedInstance{
//考虑多线程并发问题, 所以加个@synchronized同步锁
@synchronized(self) {
if (!_sharedInstance) {
_sharedInstance = [[self alloc] init];

}
}
return _sharedInstance;
}

/**
*GCD版本(线程安全)--🍎推荐
*优点:1、线程安全 2、无需担心加锁或同步
*缺点:暂无
*/
static dispatch_once_t onceToken;/*这个拿到函数体外,成为全局的,用于访问销毁单例*/
+ (instancetype)sharedInstance2 {
//dispatch_once保证线程安全
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}


#pragma mark---销毁单例
/**
* 同步锁版本:销毁单例
*/
+(void)attemptDealloc{
_sharedInstance = nil;
}


/**
* GCD版本:销毁单例
*/
+(void)attempDealloc{
onceToken = 0; // 只有置成0,GCD才会认为它从未执行过.它默认为0.这样才能保证下次再次调用shareInstance的时候,再次创建对象.
_sharedInstance = nil;
}

这样就是一个完整的单例,保证怎么创建都是唯一的.

测试单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
AccountManager *obj1 = [AccountManager sharedInstance] ;
NSLog(@"obj1 = %@", obj1) ;

AccountManager *obj2 = [AccountManager sharedInstance] ;
NSLog(@"obj2 = %@", obj2) ;

AccountManager *obj3 = [[AccountManager alloc] init] ;
NSLog(@"obj3 = %@", obj3) ;

AccountManager* obj4 = [[AccountManager alloc] init] ;
NSLog(@"obj4 = %@", [obj4 copy]) ;
}

打印详情

1
2
3
4
2016-03-12 14:30:52.078030+0800 SingletonDemo[3438:763080] obj1 = <AccountManager: 0x60400001dfb0>
2016-03-12 14:30:52.078208+0800 SingletonDemo[3438:763080] obj2 = <AccountManager: 0x60400001dfb0>
2016-03-12 14:30:52.078340+0800 SingletonDemo[3438:763080] obj3 = <AccountManager: 0x60000001e230>
2016-03-12 14:30:52.078340+0800 SingletonDemo[3438:763080] obj4 直接Crash

​ 以上创建单例方式基本可以了,通过alloccopy方式创建单例本身就是一种非规范写法,但为了提高容错率,我们还是处理下alloccopy方式创建单例。

升级版

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//
// AccountManager.m
// Copyright © 2018年 hl.com.cn. All rights reserved.
//

#import "AccountManager.h"

@implementation AccountManager

//为单例对象创建的静态实例,因为对象的唯一性,必须是static类型
static AccountManager *_sharedInstance = nil;

/**
* 最简单的单例
* 缺点:没有考虑多线程并发问题(非线程安全)
*/
+ (instancetype)sharedInstance1{
if (!_sharedInstance) {
_sharedInstance = [[self alloc]init];
}
return _sharedInstance;
}


/**
*同步锁版本
*优点:线程安全
*缺点:每次运行代码前都要获取锁,效率较GCD慢
*/
+ (instancetype)sharedInstance{
//考虑多线程并发问题, 所以加个@synchronized同步锁
@synchronized(self) {
if (!_sharedInstance) {
_sharedInstance = [[super allocWithZone:NULL] init];
}
}
return _sharedInstance;
}

/**
*GCD版本(线程安全)--🍎推荐
*优点:1、线程安全 2、无需担心加锁或同步
*缺点:暂无
*/
static dispatch_once_t onceToken;/*这个拿到函数体外,成为全局的,用于访问销毁单例*/
+ (instancetype)sharedInstance2 {
//dispatch_once保证线程安全
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}


#pragma mark---销毁单例
/**
同步锁版本:销毁单例
*/
+(void)attemptDealloc1{
_sharedInstance = nil;
}


/**
GCD版本:销毁单例
*/
+(void)attempDealloc2{
onceToken = 0; // 只有置成0,GCD才会认为它从未执行过.它默认为0.这样才能保证下次再次调用shareInstance的时候,再次创建对象.
_sharedInstance = nil;
}



#pragma mark---完善alloc,copy方式创建的单例
/**
*创建对象的步骤分为申请内存(alloc)->初始化(init)这两个步骤,我们要确保对象的唯一性,因此在第一步这个阶段我们就要拦截它。当我们调用alloc方法时,OC内部会调用allocWithZone这个方法来申请内存,我们重写这个方法,然后在这个方法中调用shareInstance方法返回单例对象,这样就可以达到我们的目的。拷贝对象也是同样的原理,重写copyWithZone方法,然后在这个方法中调用shareInstance方法返回单例对象。
* 重写allocWithZone方法,保证alloc或者init创建的实例不会产生新实例,因为该类覆盖了allocWithZone方法,所以只能通过其父类分配内存,即[super allocWithZone]
* AccountManager *account= [AccountManager alloc]init];
*
*/
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [self sharedInstance];// 直接调用单例的创建方法
}

/**
* NSCopying协议,重写copyWithZone方法,保证copy不会产生新实例,
* 保证copy时相同 防止AccountManager *account= [AccountManager copy];方式出现Crash
*/
+ (id)copyWithZone:(struct _NSZone *)zone {
return [self sharedInstance];
}
//

// 重写copy方法
- (id)copy {
return self;
}

// 重写 mutableCopy 方法
- (id)mutableCopy {
return self;
}

@end

测试打印信息:

1
2
3
4
2016-03-12 14:53:17.572240+0800 SingletonDemo[3995:849854] obj1 = <AccountManager: 0x600000007850>
2016-03-12 14:53:17.572624+0800 SingletonDemo[3995:849854] obj2 = <AccountManager: 0x600000007850>
2016-03-12 14:53:17.572768+0800 SingletonDemo[3995:849854] obj3 = <AccountManager: 0x600000007850>
2016-03-12 14:53:17.572909+0800 SingletonDemo[3995:849854] obj4 = <AccountManager: 0x600000007850>
文章作者: kyren
文章链接: http://huluo666.github.io/2016/03/12/单例写法 /
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Kyren's Blog