最近用到单例模式,可是之前仅限于理论阶段,现在要用到实际当中,难免会遇到困难。我查阅了一些资料,先粗略的谈一下。
首先说一下单例模式适用的情景吧,什么时候用单例模式比较好?
当你只想让一个类只有一个实例变量,即整个程序共享这唯一的变量时,用单例比较好。它里面包含全局的一些信息,可以为全局所用
然后说一下单例模式的构建(ARC,非ARC要多几个方法):
首先在单例类比如就把这个单例类叫“Singleton”的.h文件中声明一个创建单例的方法,比如:+ (id)shareInstance;
然后在.m中,
第一步: static Singleton *shareInstance = nil;
第二步:实现.h中声明的创建单例的方法:(懒汉模式)
+ (id) shareInstance
{
@synchronized (self) {
if( ! shareInstance ){
[ [self alloc] init ];
}
}
return sharInstance;
}
这样就创建了一个简单的单例模式,实际上有一部分程序员也是这样实现的,但实际上这是一个不“严格”版本,在实际中使用,可能会遇到发起调用的对象不能以其他分配方式实例化单例对象(比如 alloc),否则,就会创建多个实例。这样在要求只有一个实例对象的场合中就会遇到无法感知的bug。
所以在.m文件中还需要加入以下方法:
第三步:补充实现 allocWithZone copyWithZone retain release autorelease等方法
+(id) allocWithZone:(NSZone *)zone ( 因为这是创建实例的方法,所以是类方法)
{
@synchronized (self) {
if( ! shareInstance ){
[ [self alloc] init ];
}
}
return sharInstance;
}
- (id)copyWithZone: (NSZone *) zone{
return self; //保证只有一个实例变量
}
/*
非ARC中(MRC),单例模式的实现(比ARC多了几个步骤)
实现内存管理方法:
-(id)retain {return self; }
- (NSUInteger) retainCount {return 1; }
- (oneway void) release { }
- (id)autorelease {return self;}
-(id)retain{
return self;
}
- (NSUInteger) retainCount {
return NSUIntegerMax; //执行retainCount方法时返回的一个数值,这里返回一个最大值
}
- (oneway void) release {
[super release];
}
- (id) autorelease{
return self
}
*/
这样Singleton类就变得“严格”了。
解释:
1. static Singleton *shareInstance = nil;
静态全局变量,始终指向实例化出的对象。
2. +(id)shareInstance;
外界初始化得到单例类对象的接口。这个类方法返回的就是ShareInstance,即类的一个对象。如果shareInstance为空,则实例化一个对象。如果不为空,则直接返回。这样保证了实例的唯一。
3.@synchronized (self)
线程锁。保证同一时间没有其他线程对self进行修改,保证线程安全性
4. + (id)allocWithZone
执行alloc方法时,会先调用allocWithZone方法创建对象
5. - (id)copyWithZone: (NSZone *) zone;
执行copy方法时会先执行此方法。 此方法防止外界拷贝造成多个实例,保证实例的唯一性。
/*
6. - ( id )retain;
因为只有一个实例对象,所以retain不能增加引用计数
7. - (NSUInteger)retainCount
因为只有一个实例对象,设置默认引用计数。这里是取得NSUInteger的最大值,当然也可以设置成1或者其他值。
8. - (oneway void)release;
oneway void 是用于多线程编程中,表示单项执行,不能“回滚”,即原子操作
*/
另外关于创建唯一单例的方法,自从GCD出现后,又有了新的方法,即:
+ (id)shareInstance{
static Singleton *shareInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareInstance=[ [ self alloc ]init];
});
return shareInstance;
}
实现机制:
1.当调用alloc方法时,alloc方法内部会调用allocWithZone:这个方法
2.提供一个share方法,如上实例;
优缺点:
从线程安全性上讲,不加同步的懒汉式是线程不安全的,比如,有两个线程,一个是线程A,一个是线程B,它们同时调用alloc方法,那就可能导致并发问题。如上实例,可以加锁防止多线程同步的时候大家都创建对象。
懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,骄傲就不回创建实例,则节约内存空间。
饿汉模式
上面的是比较常用的懒汉模式,即当单例对象的获取方法第一次被调用时创建单例对象。下面说下饿汉模式,即类被加载时就创建单例对象。
在.m文件中做以下处理:
static Singleton *shareInstance = nil;
+ (void)Load{
shareInstance = [[self alloc] init];
}
+ (id)allocWithZone:(NSZone *)zone{
if (!shareInstance) {
shareInstance = [super allocWithZone:zone];
}
return shareInstance;
}
+ (instancetype)shareInstance{
return shareInstance;
}
实现机制:
当类加载到OC运行时环境中(内存),就会调用+ (void)load一次(一个类只会加载1次)