正确的单一模式目标C(iOS)?

时间:2011-09-29 14:26:36

标签: ios objective-c singleton grand-central-dispatch

我在网上发现了一些使用GCD创建单例类的信息。这很酷,因为它的线程安全,开销很低。遗憾的是,我找不到完整的解决方案,只能找到sharedInstance方法的片段。所以我使用试错法制作了自己的课 - 而且瞧瞧 - 以下内容出来了:

@implementation MySingleton

// MARK: -
// MARK: Singleton Pattern using GCD

+ (id)allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; }
- (id)copyWithZone:(NSZone *)zone { return self; }
- (id)autorelease { return self; }
- (oneway void)release { /* Singletons can't be released */ }
- (void)dealloc { [super dealloc]; /* should never be called */ }
- (id)retain { return self; }
- (NSUInteger)retainCount { return NSUIntegerMax; /* That's soooo non-zero */ }

+ (MySingleton *)sharedInstance
{
    static MySingleton * instance = nil;

    static dispatch_once_t predicate;   
    dispatch_once(&predicate, ^{
        // --- call to super avoids a deadlock with the above allocWithZone
        instance = [[super allocWithZone:nil] init];
    });

    return instance;
}

// MARK: -
// MARK: Initialization

- (id)init
{
    self = [super init];
    if (self) 
    {
        // Initialization code here.
    }
    return self;
}

@end

请随时发表评论并告诉我,如果我遗失了某些内容或做了一些完全错误的事情;)

干杯 斯蒂芬

3 个答案:

答案 0 :(得分:82)

保持简单:

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

- (void)dealloc
{
    // implement -dealloc & remove abort() when refactoring for
    // non-singleton use.
    abort();
}

就是这样。覆盖retainreleaseretainCount,其余的只是隐藏错误并添加一堆不必要的代码。每行代码都是等待发生的错误。实际上,如果您导致在共享实例上调用dealloc,则您的应用中存在非常严重的错误。该错误应该修复,而不是隐藏。

这种方法也适用于重构以支持非单例使用模式。几乎所有存在于单个版本之后的单例最终都会被重构为非单例形式。一些(如NSFileManager)继续支持单例模式,同时也支持任意实例化。

请注意,上述内容在ARC中也“正常”。

答案 1 :(得分:19)

// See Mike Ash "Care and Feeding of Singletons"
// See Cocoa Samurai "Singletons: You're doing them wrong"
+(MySingleton *)singleton {
    static dispatch_once_t pred;
    static MySingleton *shared = nil;
    dispatch_once(&pred, ^{
        shared = [[MySingleton alloc] init];
        shared.someIvar = @"blah";
    });
    return shared;
}

请注意dispatch_once is not reentrant,因此从dispatch_once块中调用自身会使程序死锁。

不要试图对自己进行防御性编码。如果你没有编写框架代码,请将你的类视为正常,然后坚持上面的单例习语。把单身成语想象成一种方便的方法,而不是你班级的定义特征。您希望在单元测试期间将您的类视为普通类,因此可以保留可访问的构造函数。

不要使用allocWithZone:

  • 它忽略了它的论点,其行为与alloc完全相同。 Objective-C中不再使用内存区域,因此仅保留allocWithZone:以与旧代码兼容。
  • 不起作用。您无法在Objective-C中强制执行单例行为,因为始终可以使用NSAllocateObject()class_createInstance()创建更多实例。

单件工厂方法始终返回以下三种类型之一:

  • id表示返回类型未完全知晓(建立类集群的情况)。
  • instancetype表示返回的类型是封闭类的实例。
  • 类名本身(示例中为MySingleton)以保持简单。

由于您标记了此iOS,因此单例的替代方法是将ivar保存到应用委托,然后使用便捷宏,如果您改变主意,可以重新定义:

#define coreDataManager() \
        ((AppDelegate*)[[UIApplication sharedApplication] delegate]).coreDataManager

答案 2 :(得分:1)

如果你想对你的单身人士进行单元测试,你也必须这样做,以便你可以用模拟单身人士替换它和/或将其重置为正常的单身人士:

@implementation ArticleManager

static ArticleManager *_sharedInstance = nil;
static dispatch_once_t once_token = 0;

+(ArticleManager *)sharedInstance {
    dispatch_once(&once_token, ^{
        if (_sharedInstance == nil) {
            _sharedInstance = [[ArticleManager alloc] init];
        }
    });
    return _sharedInstance;
}

+(void)setSharedInstance:(ArticleManager *)instance {
    once_token = 0; // resets the once_token so dispatch_once will run again
    _sharedInstance = instance;
}

@end
相关问题