欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

单例模式-如何保证单例对象全局唯一性?

程序员文章站 2022-05-18 14:16:34
...

单例概念:整个应用或系统只能有该类的一个实例,即是在整个项目中,这个类的对象只能被初始化一次。单例类保证了应用程序的生命周期中有且仅有一个该类的实例对象,而且易于外界访问。

“单例模式中,怎么保证这个单例对象是唯一的。或者说如果在一个对外开放的SDK中,怎么才能保证用户获得的对象是唯一的单例?”

当被问到这个问题的时候,其实是有点懵的。单例模式的单例对象不就是唯一的吗?否则怎么称之为单例模式?带着疑问,写出了常用的单例模式。


static UserDataManager *_shareManager;

+ (instancetype)shareManager {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _shareManager = [[UserDataManager alloc] init];
    });
    return _shareManager;
}

心想,都已经这个样子了,怎么会不唯一呢?那么问题出现在哪里?很容易发现通过类方法shareManager获取的单例对象是唯一的。但是只有这一种方法才能获取单例类的实例化对象吗?

并不是!

比如说,通过alloc初始化的对象。它是单例类的对象,但它并不是单例对象。所以就出现了不唯一的状况,尤其是在使用者不知情的情况下,难免会出现问题

再比如,如果实现了NSCopying协议,通过copy方法得到单例对象的复制体,也会产生不唯一的单例类的对象。

所以,上面那种写法并没有错,只是忽视了一个很重要的问题:保证单例对象的唯一性,限制其它状况的产生。

解决方法一:默默付出,将初始化与拷贝的对象指向单例对象

static UserDataManager *_shareManager;

+ (instancetype)shareManager {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _shareManager = [[super allocWithZone:NULL] init];
    });
    
    return _shareManager;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [UserDataManager shareManager];
}

- (id)copyWithZone:(NSZone *)zone {
    return [UserDataManager shareManager];
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    return [UserDataManager shareManager];
}

测试结果:

UserDataManager *manager = [[UserDataManager alloc] init];
manager.userName = @"Json";
[UserDataManager shareManager].userName = @"李四";
NSLog(@"%@",manager);
NSLog(@"%@",[UserDataManager shareManager]);
UserDataManager *managerCopy = [[UserDataManager shareManager] copy];
NSLog(@"%@",managerCopy);
    
// <UserDataManager: 0x60000374c5f0> 
// <UserDataManager: 0x60000374c5f0>
// <UserDataManager: 0x60000374c5f0>

这种情况下通过alloccopy的方式获取的依旧是唯一的单例对象。

解决方法二:强势限制,alloccopy方法中抛异常

如果没有实现NSCopying协议与copyWithZone:方法,调用copy会报错。所以这里可以只对alloc方法中设置异常提示。

+ (instancetype)alloc {
    if (_shareManager) {
        NSException *exception = [NSException exceptionWithName:@"重复创建单例对象" reason:@"未使用单例方法" userInfo:nil];
        [exception raise];
    }
    return [super alloc];
}

当程序*中止,就知道有些对象是单例对象,并不是随随便就能用的。

单例对象从初始化开始,在程序的整个证明周期内存在。