单例是在整个项目中,一个类只能创建一个实例,而且方便供外界访问。所以实现单例模式最主要的就是要实现一个类只能创建一个实例。
以SpiderMan类为例子
1、类在创建实例的过程中,都会通过alloc来获取内存空间,要想类只能创建一个实例就必须得在alloc的时候控制类只创建一个实例。每次alloc的时候都会调用+ (id)allocWithZone:(struct _NSZone *)zone方法,多以应该在该方法中对实例进行处理。
首先需要一个全局外部变量 _instance,至于为什么要使用static,后面再说明。
static id _instance;
在allocWithZone:方法中创建这个实例
+ (id)allocWithZone:(struct _NSZone *)zone
{
if (_instance == nil) { // 防止创建多次
_instance = [super allocWithZone:zone];
}
return _instance;
}
这样就基本上实现了一个类就只创建一个实例的功能。但是这并不完善,因为在多线程中,该类有可能创建不同的实例。所以为了保证该类只能创建一个实例还必须得考虑线程问题。所以为了解决该问题必须得给创建实例的代码加上一个线程锁,如:
+ (id)allocWithZone:(struct _NSZone *)zone
{
@synchronized(self) {
if (_instance == nil) { // 防止创建多次
_instance = [super allocWithZone:zone];
}
}
return _instance;
}
这样在线程上保证了单例性,但有点不足的是,每次创建实例的时候,都会重复地加锁,所以再做如下处理:
+ (id)allocWithZone:(struct _NSZone *)zone
{
if (_instance == nil) { // 防止频繁加锁
@synchronized(self) {
if (_instance == nil) { // 防止创建多次
_instance = [super allocWithZone:zone];
}
}
}
return _instance;
}
2、一般地为了方便创建实例都会提供一个类方法shareXXX,而一般不会通过alloc来创建实例,所以给SpiderMan添加一个类方法
+ (id)shanreSpiderMan
{
if (_instance == nil) { // 防止频繁加锁
@synchronized(self) {
if (_instance == nil) { // 防止创建多次
_instance = [[self alloc] init];
}
}
}
return _instance;
}
3、还需要考虑的一个问题就是:如果单实例通过copy来拷贝一个对象时,这时拷贝的对象是一个新的对象,SpiderMan有了两个不同的对象,这与初衷相悖,所以为了解决这个问题
- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
此时,单例基本上已经实现了,使用时只需要通过调用类方法shareSpiderMan就可以获得单例对象了。
现在再来看看前面提到的一个问题:为什么使用static?首先这个static可以用来修饰函数,也可以用来修饰变量,在这里修饰的是变量。另外还需要知道的一点就是,全局变量是可以在整个项目当中使用的,比如在该SpiderMan类中的_instance全局变量,如果没有加static的话,它可以在别的类中被访问到,通过extern id _instance即可以访问得到并修改该_instance。而static修饰的全局变量是仅限于当前文件内部的,外部文件是访问不了的即使是使用extern也不能访问到。
OK,这样就实现了单例模式了,详细代码已上传到Github:https://github.com/Anchlate/Singleton-SpiderMan。
完善部分请看下一篇单例模式(二)