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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7598820/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 21:41:00  来源:igfitidea点击:

Correct Singleton Pattern Objective C (iOS)?

iosobjective-csingletongrand-central-dispatch

提问by blackjacx

I found some information in the net to create a singleton class using GCD. Thats cool because it's thread-safe with very low overhead. Sadly I could not find complete solutions but only snippets of the sharedInstance method. So I made my own class using the trial and error method - and et voila - the following came out:

我在网上找到了一些使用 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

Please feel free to comment and tell me if I've missing something or doing something completely wrong ;)

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

Cheers Stefan

干杯斯特凡

回答by bbum

Keep it simple:

把事情简单化:

+(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();
}

That is it. Overriding retain, release, retainCountand the rest is just hiding bugs and adding a bunch of lines of unnecessary code. Every line of code is a bug waiting to happen. In reality, if you are causing deallocto be called on your shared instance, you have a very serious bug in your app. That bug should be fixed, not hidden.

这就对了。重写retainreleaseretainCount和剩下的只是隐藏错误和加入一些不必要的行代码。每一行代码都是一个等待发生的错误。实际上,如果您导致dealloc在共享实例上被调用,则您的应用程序中存在非常严重的错误。这个错误应该被修复,而不是隐藏。

This approach also lends itself to refactoring to support non-singleton usage modes. Pretty much every singleton that survives beyond a few releases will eventually be refactored into a non-singleton form. Some (like NSFileManager) continue to support a singleton mode while also supporting arbitrary instantiation.

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

Note that the above also "just works" in ARC.

请注意,上述内容在 ARC 中也“有效”。

回答by Jano

// 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;
}

Be aware that dispatch_once is not reentrant, so calling itself from inside the dispatch_once block will deadlock the program.

请注意dispatch_once 不是可重入的,因此从 dispatch_once 块内部调用自身将使程序死锁。

Don't try to code defensively against yourself. If you are not coding a framework, treat your class as normal then stick the singleton idiom above. Think of the singleton idiom as a convenience method, not as a defining trait of your class. You want to treat your class as a normal class during unit testing, so it's OK to leave an accessible constructor.

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

Don't bother using allocWithZone:

不要打扰使用 allocWithZone:

  • It ignores its argument and behaves exactly like alloc. Memory zones are no longer used in Objective-C so allocWithZone:is only kept for compatibility with old code.
  • It doesn't work. You can't enforce singleton behavior in Objective-C because more instances can always be created using NSAllocateObject()and class_createInstance().
  • 它忽略它的论点并且表现得与 完全一样alloc。内存区域在 Objective-C 中不再使用,因此allocWithZone:仅保留用于与旧代码兼容。
  • 它不起作用。您不能在 Objective-C 中强制执行单例行为,因为始终可以使用NSAllocateObject()和创建更多实例class_createInstance()

A singleton factory method always returns one of these three types:

单例工厂方法总是返回以下三种类型之一:

  • idto indicate the return type is not fully known (case where you are building a class cluster).
  • instancetypeto indicate that the type returned is an instance of the enclosing class.
  • The class name itself (MySingletonin the example) to keep it simple.
  • id指示返回类型不完全已知(在您构建类集群的情况下)。
  • instancetype指示返回的类型是封闭类的实例。
  • 类名本身(MySingleton在示例中)以保持简单。

Since you tagged this iOS, an alternative to a singleton is saving the ivar to the app delegate and then using a convenience macro that you can redefine if you change your mind:

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

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

回答by ToddH

If you want to unit test your singleton you also have to make it so that you can replace it with a mock singleton and/or reset it to the normal one:

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

@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