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
#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 来创建实例.
推荐阅读
* Preventing other instances of your dispatch_once’d singleton
附上完整实现.
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 谈起 讲的很好.