一. 单例模式简介
-
单例模式的作用
可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问
从而方便地控制了实例个数,并节约系统资源 -
单例模式的使用场合
在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次),一般用于工具类。例如:登陆控制器,网络数据请求,音乐播放器等一个工程需要使用多次的控制器或方法。 -
单例模式的优缺点
优点:
单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
单例模式因为类控制了实例化过程,所以类可以更加灵活修改实例化过程。
缺点:
单例对象一旦建立,对象指针是保存在静态区的,单例对象在堆中分配的内存空间,会在应用程序终止后才会被释放。
单例类无法继承,因此很难进行类的扩展。
单例不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
注意:我们在使用单例类之前,一定要考虑好单例类是否适合和类以后的扩展性,避免盲目滥用单例
最近看到一些github上的单例使用,别人的用法,有一些思考,然后写demo测试了下,就这个简单的单例也有一些坑呢,希望能给他人一些提醒。
Objective-C中的单例
我们通常在OC中实现一个单例方法都是这样:
1
2
3
4
5
6
7
8
9
|
static HLTestObject *instance = nil;
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[[self class] alloc] init];
});
return
instance;
}
|
可是这样就可以了么?我做了如下测试:
1
2
3
4
5
6
|
HLTestObject *objct1 = [HLTestObject sharedInstance];
NSLog(@
"%@"
,objct1);
HLTestObject *objc2 = [[HLTestObject alloc] init];
NSLog(@
"%@"
,objc2);
HLTestObject *objc3 = [HLTestObject
new
];
NSLog(@
"%@"
,objc3);
|
看到这个测试,你想到打印结果了么?结果是这样的:
1
2
3
|
2016-05-23 12:52:57.095 PractiseProject[3579:81998] <hltestobject: 0x7fcf39515510>
2016-05-23 12:52:57.095 PractiseProject[3579:81998] <hltestobject: 0x7fcf395c4b70>
2016-05-23 12:52:57.095 PractiseProject[3579:81998] <hltestobject: 0x7fcf395c6890></hltestobject: 0x7fcf395c6890></hltestobject: 0x7fcf395c4b70></hltestobject: 0x7fcf39515510>
|
很明显,通过三种方式创建出来的是不同的实例对象,这就违背了单例类有且仅有一个实例的定义。
为了防止别人不小心利用alloc/init方式创建示例,也为了防止别人故意为之,我们要保证不管用什么方式创建都只能是同一个实例对象,这就得重写另一个方法,实现如下:
1
2
3
4
5
6
7
8
|
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [
super
allocWithZone:zone];
});
return
instance;
}
|
再次用上面的测试代码,结果是这样的:
1
2
3
|
2016-05-23 12:57:37.396 PractiseProject[3618:83975] <hltestobject: 0x7f88b9488ac0>
2016-05-23 12:57:37.396 PractiseProject[3618:83975] <hltestobject: 0x7f88b9488ac0>
2016-05-23 12:57:37.396 PractiseProject[3618:83975] <hltestobject: 0x7f88b9488ac0></hltestobject: 0x7f88b9488ac0></hltestobject: 0x7f88b9488ac0></hltestobject: 0x7f88b9488ac0>
|
好像用不同的构造方法,获取的都是同一个对象,你以为这样就完了?还早着呢!
一般我们的类里肯定都会有一些属性,然后我就添加了两个property:
1
2
3
|
@property (assign, nonatomic) int height;
@property (strong, nonatomic) NSObject *object;
@property (strong, nonatomic) NSMutableArray *arrayM;
|
而一些对象类的初始化,或者基础类型的默认值设置都是在init方法里,就像这样:
1
2
3
4
5
6
7
8
9
10
|
- (instancetype)init
{
self = [
super
init];
if
(self) {
_height = 10;
_object = [[NSObject alloc] init];
_arrayM = [[NSMutableArray alloc] init];
}
return
self;
}
|
我重写了HLTestObject类的description方法:
1
2
3
4
5
6
7
8
9
|
- (NSString *)description
{
NSString *result = @
""
;
result = [result stringByAppendingFormat:@
"<%@: %p>"
,[self class], self];
result = [result stringByAppendingFormat:@
" height = %d,"
,self.height];
result = [result stringByAppendingFormat:@
" arrayM = %p,"
,self.arrayM];
result = [result stringByAppendingFormat:@
" object = %p,"
,self.object];
return
result;
}
|
还是用上面的测试代码,测试结果是这样的:
1
2
3
|
2016-05-23 13:14:43.684 PractiseProject[3781:92758] <hltestobject: 0x7f8a5b458450> height = 20, arrayM = 0x7f8a5b422940, object = 0x7f8a5b4544e0,
2016-05-23 13:14:43.684 PractiseProject[3781:92758] <hltestobject: 0x7f8a5b458450> height = 10, arrayM = 0x7f8a5b4552e0, object = 0x7f8a5b45a710,
2016-05-23 13:14:43.684 PractiseProject[3781:92758] <hltestobject: 0x7f8a5b458450> height = 10, arrayM = 0x7f8a5b459770, object = 0x7f8a5b4544e0,</hltestobject: 0x7f8a5b458450></hltestobject: 0x7f8a5b458450></hltestobject: 0x7f8a5b458450>
|
可以看到,尽管使用的是同一个示例,可是他们的property值却不一样。
因为尽管没有为示例重新分配内存空间,但是因为又执行了init方法,会导致property被重新初始化。
所以我们需要修改单例的实现。
第一种:
可以将property的初始化或者默认值设置放到dispatch_once 的block内部:
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
|
static HLTestObject *instance = nil;
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[[self class] alloc] init];
instance.height = 10;
instance.object = [[NSObject alloc] init];
instance.arrayM = [[NSMutableArray alloc] init];
});
return
instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [
super
allocWithZone:zone];
});
return
instance;
}
- (NSString *)description
{
NSString *result = @
""
;
result = [result stringByAppendingFormat:@
"<%@: %p>"
,[self class], self];
result = [result stringByAppendingFormat:@
" height = %d,"
,self.height];
result = [result stringByAppendingFormat:@
" arrayM = %p,"
,self.arrayM];
result = [result stringByAppendingFormat:@
" object = %p,"
,self.object];
return
result;
}
|
来看看测试结果:
1
2
3
|
2016-05-23 13:29:14.856 PractiseProject[3909:99058] <hltestobject: 0x7fa72270c570> height = 20, arrayM = 0x7fa722716c10, object = 0x7fa7227140e0,
2016-05-23 13:29:14.856 PractiseProject[3909:99058] <hltestobject: 0x7fa72270c570> height = 20, arrayM = 0x7fa722716c10, object = 0x7fa7227140e0,
2016-05-23 13:29:14.856 PractiseProject[3909:99058] <hltestobject: 0x7fa72270c570> height = 20, arrayM = 0x7fa722716c10, object = 0x7fa7227140e0,</hltestobject: 0x7fa72270c570></hltestobject: 0x7fa72270c570></hltestobject: 0x7fa72270c570>
|
第二种:
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
|
static HLTestObject *instance = nil;
+ (instancetype)sharedInstance
{
return
[[self alloc] init];
}
- (instancetype)init
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [
super
init];
instance.height = 10;
instance.object = [[NSObject alloc] init];
instance.arrayM = [[NSMutableArray alloc] init];
});
return
instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [
super
allocWithZone:zone];
});
return
instance;
}
- (NSString *)description
{
NSString *result = @
""
;
result = [result stringByAppendingFormat:@
"<%@: %p>"
,[self class], self];
result = [result stringByAppendingFormat:@
" height = %d,"
,self.height];
result = [result stringByAppendingFormat:@
" arrayM = %p,"
,self.arrayM];
result = [result stringByAppendingFormat:@
" object = %p,"
,self.object];
return
result;
}
|
测试结果:
1
2
3
|
2016-05-23 13:31:44.824 PractiseProject[3939:100662] <hltestobject: 0x7fa9da711a70> height = 20, arrayM = 0x7fa9da707ca0, object = 0x7fa9da70a940,
2016-05-23 13:31:44.825 PractiseProject[3939:100662] <hltestobject: 0x7fa9da711a70> height = 20, arrayM = 0x7fa9da707ca0, object = 0x7fa9da70a940,
2016-05-23 13:31:44.825 PractiseProject[3939:100662] <hltestobject: 0x7fa9da711a70> height = 20, arrayM = 0x7fa9da707ca0, object = 0x7fa9da70a940,</hltestobject: 0x7fa9da711a70></hltestobject: 0x7fa9da711a70></hltestobject: 0x7fa9da711a70>
|
注意:
以上代码均是使用ARC的方式管理内存,如果你还在使用MRC(这也太不与时俱进了)。那你还需要重写 retain 和release方法,防止示例引用计数的改变。
Swift中的单例
利用Swift中的一些特性,Swift中的单例可以超级简单,like this:
1
2
3
|
class HLTestObject: NSObject {
static let sharedInstance = HLTestObject();
}
|
可是这样就完了么?同样写一段测试代码:
1
2
3
4
|
let object1 = HLTestObject.sharedInstance;
print(object1);
let object2 = HLTestObject();
print(object2);
|
打印结果却是这样的:
1
2
|
<swiftproject.hltestobject: 0x7f90ebc74e50>
<swiftproject.hltestobject: 0x7f90ebe5cf40></swiftproject.hltestobject: 0x7f90ebe5cf40></swiftproject.hltestobject: 0x7f90ebc74e50>
|
所以,我们必须禁用到构造方法:
1
2
3
4
5
|
class HLTestObject: NSObject {
static let sharedInstance = HLTestObject();
private override init() {
}
}
|
如果有实例属性需要初始化,就可以这样:
1
2
3
4
5
6
7
8
9
10
11
|
class HLTestObject: NSObject {
var
height = 10;
var
arrayM: NSMutableArray
var
object: NSObject
static let sharedInstance = HLTestObject();
private override init() {
object = NSObject()
arrayM = NSMutableArray()
super
.init()
}
}
|
当然,由于Swift的特性,在Swift中创建单例的方式也不止一种,需要注意的是要确保该类有且仅有一个实例就OK了。
结语:
综上如果要简单使用单例就用shareInstance这个单例方法,但是调用单例就只用shareInstance这个单例方法,不要用alloc,new。如过没有使用shareInstance方法就需要多写initWithZone。