OC中单例的实现原理

我们知道单例是在整个工程当中只有一个该类实例,怎么才能保证每次都只返回一个实例而不是另外一个实例呢?

  • 单例是一个对象,也是要被创建和初始化的,只是为了实现全局就创建一次,我们做了一些处理,整个工程当中只有一个该类实例。

一、最常见写单例的方式

这里创建了一个类TestClass,实现了一下大家平时写的单例
.h

#import 

@interface SingleTestClass : NSObject

+ (instancetype)sharedInstance;

@end

.m

#import "SingleTestClass.h"

@implementation SingleTestClass

+ (instancetype)sharedInstance {
    static SingleTestClass *single = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        single = [[self alloc] init];
    });
    return single;
}
@end

ok完事了,一个单例就这样产生了?用了onceToken,在其里面alloc init,那整个项目中就只有这一个实例了吧。其实不然。

  • 这样写能保证我们每次调用sharedInstance的时候返回的是同一个实例,也就是我们在onceToken里面创建的实例。dispatch_once(&onceToken, ^{
    single = [[self alloc] init];
    });方法保证只执行一遍

之所以这样能只返回一个实例的前提是,每次使用该类的对象的时候只通过提供的这个-(id)sharedInstance方法,才能保证每次都只返回一个实例而不是另外一个实例。

换句话说我们如果使用了alloc init来创建该类的对象,那肯定返回的就是另一个实例了。当然你可能会想,为什么要alloc init呢,用sharedInstance不就行了,我反问一句,为什么不能alloc init呢,难道不是OC对象?

  • 上述写法是不能完全保证每次都只返回一个实例而不是另外一个实例

二、sharedInstance方法的局限性

我们这样来看一下就知道了

SingleTestClass *testClass1 = [[SingleTestClass alloc] init];
SingleTestClass *testClass2 = [[SingleTestClass alloc] init];
SingleTestClass *testClass3 = [[SingleTestClass alloc] init];

SingleTestClass *shardeClass1 = [SingleTestClass sharedInstance];
SingleTestClass *shardeClass2 = [SingleTestClass sharedInstance];
SingleTestClass *shardeClass3 = [SingleTestClass sharedInstance];

NSLog(@"%@ \n %@ \n %@ \n %@ \n %@ \n %@ ",testClass1,testClass2,testClass3,shardeClass1,shardeClass2,shardeClass3);

我们以不同的形式创建了6个对象,打印了地址,结果显示如下:

 
  
  
  
  
  

用sharedInstance每次返回的是同一个实例,但是用alloc init每次会返回新的实例。

也就是说每次alloc init都会分配新的内存空间(关于内存空间这点看这),而每次sharedInstance则不会。

可以说这样写单例是不能完全保证安全的,那么怎么才能安全呢?

三、复写allocWithZone方法

通过重载父类创建对象时候分配内存地址的方法,我们让每次创建对象都只返回同一个内存空间,这样就能实现更加安全的单例模式。

关于allocWithZone方法,看这里。

重写allocWithZone:

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static SingleTestClass *singleClass = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{ 
        singleClass = [super allocWithZone:zone];
    });
    return singleClass;
}
@end

重载这个初始化方法,使得每次不管什么方式,创建该类的对象的时候都返回一个实例,也就是一个单例。实质上每次alloc的时候都将指正指向同一个内存空间。

为了方便外部调用我们还向外提供了一个类方法sharedInstance,返回该类的一个实例。

@implementation SingleTestClass

+ (instancetype)sharedInstance {
    return [[self alloc] init];
}

- (instancetype)init {
    if (self = [super init]) {
        
    }
    return self;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static SingleTestClass *singleClass = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        singleClass = [super allocWithZone:zone];//最先执行,只执行了一次
    });
    return singleClass;
}
@end

下面来验证一下:

SingleTestClass *testClass1 = [[SingleTestClass alloc] init];
SingleTestClass *testClass2 = [[SingleTestClass alloc] init];
SingleTestClass *testClass3 = [[SingleTestClass alloc] init];
SingleTestClass *shardeClass1 = [SingleTestClass sharedInstance];
SingleTestClass *shardeClass2 = [SingleTestClass sharedInstance];
SingleTestClass *shardeClass3 = [SingleTestClass sharedInstance];
NSLog(@"%@ \n %@ \n %@ \n %@ \n %@ \n %@ ",testClass1,testClass2,testClass3,shardeClass1,shardeClass2,shardeClass3);

打印结果:

 
  
  
  
  
 

的确是返回来了同一个实例,不管用alloc init创建还是直接用sharedInstance这个方法。

最后

这时候你应该想起来了,为什么面试官当初问你,这样写单例有什么有缺点,或者单例怎么写才最安全。

建议两篇结合看:
第一篇
第二篇即本篇。

你可能感兴趣的:(OC中单例的实现原理)