【问题标题】:Correct Singleton Pattern Objective C (iOS)?正确的单例模式目标 C(iOS)?
【发布时间】:2011-09-29 14:26:36
【问题描述】:

我在网上找到了一些使用 GCD 创建单例类的信息。这很酷,因为它是线程安全的,开销非常低。遗憾的是,我找不到完整的解决方案,只能找到 sharedInstance 方法的 sn-ps。因此,我使用试错法制作了自己的课程 - 等等,结果如下:

@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

如果我遗漏了什么或做错了什么,请随时发表评论并告诉我;)

干杯 斯蒂芬

【问题讨论】:

  • 我很想添加一个引发异常的-(void)dealloc,这样如果有人得到一个单例实例然后释放它,你应该能够追踪有问题的演员。除了滥用该模式之外,这还会给您留下一个悬空指针。
  • 元问题:应该在 [codereview.stackexchange.com/]?
  • 苹果强烈建议不要创建覆盖保留/释放的单例!这将中断向 ARC 过渡的应用程序

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


【解决方案1】:

保持简单:

+(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 中也“有效”。

【讨论】:

  • 谢谢你...这里只是一个关于静态创建对象的问题。你不应该释放那个我dealloc的对象吗?我总是对静态对象的所有权感到困惑。所以你不应该有类似 [[MyClass sharedInstance] release];在dealloc中?
  • 单例从被请求的那一刻起一直存在,直到应用程序终止。它们不会被释放和重新实例化。由于没有理由在应用程序终止时释放任何东西,因此没有理由 release 单例。因为不太可能测试过单例的破坏,所以如图所示实现dealloc 是一种纯粹的防御措施,以提醒未来您过去没有考虑过此类的内存​​管理。
  • 一个小补充,(id) 应该是 (instanceType)..详情请阅读this question
  • 您好,您可以使用 swift 语言吗?谢谢。
  • @Allan,我添加了另一个 answer 用于使用 Swift 创建单例。
【解决方案2】:
// 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

【讨论】:

    【解决方案3】:

    如果你想对你的单例进行单元测试,你还必须制作它,以便你可以用模拟单例替换它和/或将其重置为正常的:

    @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
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-06-17
    • 1970-01-01
    • 2015-09-26
    • 2018-02-05
    • 1970-01-01
    • 2013-07-01
    • 2014-06-05
    相关资源
    最近更新 更多