iOS单例模式,你真的写对了吗?

单例类的生命周期

单例实例在存储器的中位置

请看下面的表格展示了程序中中不同的变量在手机存储器中的存储位置;

在程序中,一个单例类在程序中只能初始化一次,为了保证在使用中始终都是存在的,所以单例是在存储器的全局区域,在编译时分配内存,只要程序还在运行就会一直占用内存,在APP结束后由系统释放这部分内存内存。

单例模式很常见,但是,能真正把单利模式写对的却很少。在iOS中,一般我们都是用官方推荐的写法来写单例:

+ (instancetype)sharedInstance {

    static WQSQLiteManager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[WQSQLiteManager alloc] init];
    });
    return instance;
}

这也是我们单例模式的标准写法。 在使用时,我们只需要

WQSQLiteManager *manager = [WQSQLiteManager sharedInstance];

问题:

可是,在多人开发中,并不能保证所有人都会使用 sharedInstance 方法来创建对象;而一旦有人用 alloc,new 等来创建对象,这就不是单例了。例如:

WQSQLiteManager *a = [WQSQLiteManager sharedInstance];
WQSQLiteManager *b = [[WQSQLiteManager alloc] init];
WQSQLiteManager *c = [WQSQLiteManager new];

让我们来打印 a、b、c内存地址:

a:<WQSQLiteManager: 0x600001f1fdf0>
b:<WQSQLiteManager: 0x600001f1fe00>
c:<WQSQLiteManager: 0x600001f1fe10>

可以看出, a、b、c 不是同一个对象,而所谓单例,就是不管我用何种方法创建对象,都必须是同一个。所以,单例模式,绝不是一个 sharedInstance 就够了。

方案一

那么如何避免这种问题呢?我们知道:在对象创建的时候,alloc、new都会调用到 allocWithZone: 方法;使用拷贝创建对象时,会调用 copyWithZone: 、mutableCopyWithZone:方法;那么,重写这些方法,就可以让创建的对象唯一。

+ (instancetype)sharedInstance {

    static WQSQLiteManager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    //   instance = [[WQSQLiteManager alloc] init];

   instance = [[super allocWithZone:nil] init];
});
    return instance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [WQSQLiteManager sharedInstance];
}
- (instancetype)copyWithZone:(NSZone *)zone {
    return [WQSQLiteManager sharedInstance];
}
- (instancetype)mutableCopyWithZone:(NSZone *)zone     {
        return [WQSQLiteManager sharedInstance];
}

再运行,看一看a、b、c:

a:<WQSQLiteManager: 0x60000331d690>
a:<WQSQLiteManager: 0x60000331d690>
a:<WQSQLiteManager: 0x60000331d690>

初始化一个对象的时候,[[Class alloc] init],其实是做了两件事。
alloc 给对象分配内存空间,init是对对象的初始化,包括设置成员变量初值这些工作。
而给对象分配空间,除了alloc方法之外,还有另一个方法: allocWithZone.
使用alloc方法初始化一个类的实例的时候,默认是调用了allocWithZone的方法

方案二

还有一种方法,就是直接禁用掉 alloc、new 、copy等方法:

// 告诉编译器该方法不可用,如果强行调用编译器会提示错误
+ (instancetype)alloc UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new __attribute__((unavailable("replace with 'sharedInstance'")));
- (instancetype)copy __attribute__((unavailable("replace with 'sharedInstance'")));
- (instancetype)mutableCopy __attribute__((unavailable("replace with 'sharedInstance'")));


+ (instancetype)sharedInstance {

static WQSQLiteManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

    //instance = [[WQSQLiteManager alloc] init];

    instance = [[super allocWithZone:nil] init];
});
return instance;
}

那么,在调用这些方法的时候就会报错,

以此达到单例模式的要求,始终只有一个对象。