OC坑集: ARC单例模式

OC 的单例也是比较让人蛋疼的.


ToolManager.h

#import <Foundation/Foundation.h>

@interface ToolManager : NSObject

@property (copy, nonatomic) NSString *tName;

+ (ToolManager *)sharedToolManager;

@end


ToolManager.m

#import "ToolManager.h"

@implementation ToolManager

+ (ToolManager *)sharedToolManager
{
    static ToolManager *manager;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        manager = [[ToolManager alloc] init];
    });
    
    return manager;
}

- (instancetype)init
{
    if (self = [super init]) {
        
    }
    
    return self;
}

上面使用 GCD 实现单例.


你很快就会发现, 这种单例的实现虽然

1. 线程安全.

2. 兼容 ARC.

3. 代码简洁.

但是, 无法控制调用者再次创建新的对象.

ToolManager *m1 = [ToolManager sharedToolManager];
m1.tName = @"m1's tName";
        
ToolManager *m2 = [[ToolManager alloc] init];
m2.tName = @"m2's tName";
        
NSLog(@"m1 = %@ | tName = %@", m1, m1.tName);
NSLog(@"m2 = %@ | tName = %@", m2, m2.tName);

很明显, m1 与 m2 不是同一个实例对象.


解决方案 1: 覆写 allocWithZone

+ (id)allocWithZone:(NSZone *)zone {
    NSString *reason = [NSString stringWithFormat:@"Attempt to allocate a second instance of the singleton %@", [self class]];
    NSException *exception = [NSException exceptionWithName:@"Multiple singletons" reason:reason userInfo:nil];
    [exception raise];
    
    return nil;
}

这样不管你是执行

[ToolManager alloc] init]
还是执行

[ToolManager new]
都会 crash, 并给出上面的提示信息.

修改 ToolManager.m 代码示例

#import "ToolManager.h"

@implementation ToolManager

+ (ToolManager *)sharedToolManager
{
    static ToolManager *manager;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        manager = [[super allocWithZone:nil] init];
    });
    
    return manager;
}

- (instancetype)init
{
    if (self = [super init]) {
        //初始化
    }
    
    return self;
}

+ (id)allocWithZone:(NSZone *)zone {
    NSString *reason = [NSString stringWithFormat:@"Attempt to allocate a second instance of the singleton %@", [self class]];
    NSException *exception = [NSException exceptionWithName:@"Multiple singletons" reason:reason userInfo:nil];
    [exception raise];
    
    return nil;
}

@end

解决方案 2: 使用 __attribute__ 并结合方案1.

ToolManager.h

#import <Foundation/Foundation.h>

@interface ToolManager : NSObject

@property (copy, nonatomic) NSString *tName;

+ (ToolManager *)sharedToolManager;

// clue for improper use (produces compile time error)
+ (instancetype) alloc          __attribute__((unavailable("alloc not available, call sharedToolManager instead")));
- (instancetype) init           __attribute__((unavailable("init not available, call sharedToolManager instead")));
+ (instancetype) new            __attribute__((unavailable("new not available, call sharedToolManager instead")));

@end

ToolManager.m

#import "ToolManager.h"

@implementation ToolManager

+ (ToolManager *)sharedToolManager
{
    static ToolManager *manager;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        manager = [[super alloc] _initInstance];
    });
    
    return manager;
}

- (instancetype)init
{
    if (self = [super init]) {
        //初始化
    }
    
    return self;
}

- (instancetype)_initInstance
{
    return [super init];
}

+ (id)allocWithZone:(NSZone *)zone {
    NSString *reason = [NSString stringWithFormat:@"Attempt to allocate a second instance of the singleton %@", [self class]];
    NSException *exception = [NSException exceptionWithName:@"Multiple singletons" reason:reason userInfo:nil];
    [exception raise];
    
    return nil;
}

@end

这种方案相比第一种方案的好处,  就是在编译时就会提示错误信息.

如果不结合方案1的话, 调用者可以通过

[ToolManager allocWithZone:nil];
来创建新的对象实例.

附: github 上写的 gist

MySingleton.h

#import <Foundation/Foundation.h>

@interface MySingleton : NSObject

+(instancetype) sharedInstance;

// clue for improper use (produces compile time error)
+(instancetype) alloc __attribute__((unavailable("alloc not available, call sharedInstance instead")));
-(instancetype) init  __attribute__((unavailable("init not available, call sharedInstance instead")));
+(instancetype) new   __attribute__((unavailable("new not available, call sharedInstance instead")));

@end


MySingleton.m

#import "MySingleton.h"

@implementation MySingleton

+(instancetype) sharedInstance {
    static dispatch_once_t pred;
    static id shared = nil;
    dispatch_once(&pred, ^{
        shared = [[super alloc] initUniqueInstance];
    });
    return shared;
}

-(instancetype) initUniqueInstance {
    return [super init];
}

@end

上面说过, 这种方式无法阻止调用者通过调用 allocWithZone 来创建实例.


推荐阅读

Singletons in Objective-C

Preventing other instances of your dispatch_once’d singleton

使用dispatch_once实现单例模式


附上完整实现.


ToolManager.h

#import <Foundation/Foundation.h>

@interface ToolManager : NSObject

@property (copy, nonatomic) NSString *tName;

+ (instancetype)sharedToolManager;

// clue for improper use (produces compile time error)
+ (instancetype) alloc __attribute__((unavailable("alloc not available, call sharedToolManager instead")));
- (instancetype) init  __attribute__((unavailable("init not available, call sharedToolManager instead")));
+ (instancetype) new   __attribute__((unavailable("new not available, call sharedToolManager instead")));
- (instancetype) copy __attribute__((unavailable("copy not available, call sharedToolManager instead")));

@end

ToolManager.m

#import "ToolManager.h"

@implementation ToolManager

+ (instancetype)sharedToolManager
{
    static ToolManager *manager;
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        manager = [[super allocWithZone:NULL] initUniqueInstance];
    });
    
    return manager;
}

- (instancetype)init
{
    if (self = [super init]) {
        //初始化
    }
    
    return self;
}

- (instancetype)initUniqueInstance
{
    return [super init];
}

+ (id)allocWithZone:(NSZone *)zone {
    NSString *reason = [NSString stringWithFormat:@"Attempt to allocate a second instance of the singleton %@", [self class]];
    NSException *exception = [NSException exceptionWithName:@"Multiple singletons" reason:reason userInfo:nil];
    [exception raise];
    
    return nil;
    
#if 0 //可以选择这种方式
    return [self sharedToolManager];
#endif
}

- (void)dealloc
{
    //释放你需要的资源, 如果有必要的话
    _tName = nil;
}

@end


在  iOS: How can I destroy a Singleton in ARC? Should I? 回答上面, 解释了单例为何不需要去释放.

被采纳的回答原文

If you destroy this singleton, you'll never be able to create it again (that's what the dispatch_once call means).

You don't need to destroy the singleton. By all means have a method on the singleton that removes any instance variables you no longer need, but there is no need to do anything else.

有些朋友可能对 allocWithZone不是很熟悉, 那么 alloc 和它有什么关系?

从 Objective-C 里的 Alloc 和 AllocWithZone 谈起 讲的很好.



©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页