【问题标题】:Create singleton using GCD's dispatch_once in Objective-C在 Objective-C 中使用 GCD 的 dispatch_once 创建单例
【发布时间】:2011-08-08 20:49:16
【问题描述】:

如果你可以针对 iOS 4.0 或更高版本

使用 GCD,在 Objective-C(线程安全)中创建单例是最好的方法吗?

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

【问题讨论】:

  • 有没有办法阻止类的用户调用alloc/copy?
  • dispatch_once_t 和 dispatch_once 似乎是在 4.0 而不是 4.1 中引入的(参见:developer.apple.com/library/ios/#documentation/Performance/…
  • 如果 init 需要使用单例对象,则此方法会出现问题。 Matt Gallagher 的代码曾多次为我工作。 cocoawithlove.com/2008/11/…
  • 我知道在这个例子中它无关紧要;但是为什么人们不更多地使用“新”。 dispatch_once(&once, ^{sharedInstance=[self new];} 只是看起来更整洁一些。相当于alloc+init。
  • 一定要开始使用返回类型instancetype。使用它而不是 id 时,代码完成要好得多。

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


【解决方案1】:

这是创建类实例的完全可接受且线程安全的方法。从技术上讲,它可能不是“单例”(因为这些对象只能有 1 个),但只要您只使用 [Foo sharedFoo] 方法访问该对象,这就足够了。

【讨论】:

  • 你如何发布它?
  • @samvermette 你不知道。单例的意义在于它将永远存在。因此,您不会释放它,并且内存会随着进程退出而被回收。
  • @Dave DeLong:在我看来,拥有单例的目的不是确定它的不朽,而是确定我们有一个实例。如果那个单例减少一个信号量怎么办?你不能随便说它永远存在。
  • @hooleyhoop 是的,在its documentation。 “如果从多个线程同时调用,此函数会同步等待,直到块完成。”
  • @WalterMartinVargas-Pena 强引用由静态变量持有
【解决方案2】:

Dave 是正确的,这很好。您可能想查看Apple's docs on creating a singleton 以获取有关实现其他一些方法的提示,以确保在类选择不使用 sharedFoo 方法时只能创建一个。

【讨论】:

  • 嗯...这不是创建单例的最佳示例。不需要重写内存管理方法。
  • 使用 ARC 完全无效。
  • 被引文献已停用。此外,仅作为外部内容链接的答案通常是较差的 SO 答案。至少摘录您答案中的相关部分。除非您想为后代保存旧方法,否则不要在这里打扰。
【解决方案3】:

您可以通过覆盖 alloc 方法来避免分配类。

@implementation MyClass

static BOOL useinside = NO;
static id _sharedObject = nil;


+(id) alloc {
    if (!useinside) {
        @throw [NSException exceptionWithName:@"Singleton Vialotaion" reason:@"You are violating the singleton class usage. Please call +sharedInstance method" userInfo:nil];
    }
    else {
        return [super alloc];
    }
}

+(id)sharedInstance
{
    static dispatch_once_t p = 0;
    dispatch_once(&p, ^{
        useinside = YES;
        _sharedObject = [[MyClass alloc] init];
        useinside = NO;
    });   
    // returns the same object each time
    return _sharedObject;
}

【讨论】:

  • 这回答了我在上面 cmets 中的问题。并不是说我非常喜欢防御性编程,而是……
【解决方案4】:

实例类型

instancetype 只是Objective-C 的众多语言扩展之一,每个新版本都会添加更多语言。

知道,喜欢它。

并以此为例说明关注底层细节如何让您深入了解转换 Objective-C 的强大新方法。

Refer here: instancetype


+ (instancetype)sharedInstance
{
    static dispatch_once_t once;
    static id sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

+ (Class*)sharedInstance
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        sharedInstance = [self new];
    });    
    return sharedInstance;
}

【讨论】:

  • 惊人的提示,谢谢! instancetype 是一个上下文关键字,可用作结果类型来表示方法返回相关的结果类型。 ... 使用 instancetype,编译器将正确推断类型。
  • 我不清楚这两个sn-ps是什么意思,它们是等价的吗?一个比另一个更可取?如果作者能为此添加一点解释就好了。
【解决方案5】:

MySingleton.h

@interface MySingleton : NSObject

+(instancetype)sharedInstance;

+(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")));
-(instancetype)copy __attribute__((unavailable("copy not available, call sharedInstance instead")));

@end

MySingleton.m

@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

【讨论】:

  • init怎么不可用?不是至少有一个init可用吗?
  • Singleton 应该只有一个接入点。而这一点就是sharedInstance。如果我们在 *.h 文件中有 init 方法,那么您可以创建另一个单例实例。这与单例的定义相矛盾。
  • @asma22 __attribute__((unavailable()) 使这些方法无法使用。如果另一个程序员想要将使用方法标记为不可用,他会得到错误
  • 我完全明白,我很高兴我学到了一些新东西,你的回答没有错,只是新手可能有点困惑......
  • 这仅适用于MySingleton,例如在MySingleton.m 我正在调用[super alloc]
【解决方案6】:

如果您想确保 [[MyClass alloc] init] 返回与 sharedInstance 相同的对象(我认为这不是必需的,但有些人想要它),可以使用第二个 dispatch_once 轻松安全地完成:

- (instancetype)init
{
    static dispatch_once_t once;
    static Class *sharedInstance;

    dispatch_once(&once, ^
    {
        // Your normal init code goes here. 
        sharedInstance = self;
    });

    return sharedInstance;
}

这允许 [[MyClass alloc] init] 和 [MyClass sharedInstance] 的任意组合返回相同的对象; [MyClass sharedInstance] 会更有效率。工作原理: [MyClass sharedInstance] 将调用 [[MyClass alloc] init] 一次。其他代码也可以多次调用它。 init 的第一个调用者将执行“正常”初始化并将单例对象存储在 init 方法中。以后对 init 的任何调用都将完全忽略 alloc 返回的内容并返回相同的 sharedInstance; alloc 的结果将被释放。

+sharedInstance 方法将像往常一样工作。如果它不是第一个调用 [[MyClass alloc] init] 的调用者,那么 init 的结果不是 alloc 调用的结果,但这没关系。

【讨论】:

    【解决方案7】:

    要创建线程安全的单例,您可以这样做:

    @interface SomeManager : NSObject
    + (id)sharedManager;
    @end
    
    /* thread safe */
    @implementation SomeManager
    
    static id sharedManager = nil;
    
    + (void)initialize {
        if (self == [SomeManager class]) {
            sharedManager = [[self alloc] init];
        }
    }
    
    + (id)sharedManager {
        return sharedManager;
    }
    @end
    

    这个博客很好地解释了单例singletons in objc/cocoa

    【讨论】:

    • 您正在链接到一篇非常古老的文章,而 OP 要求提供有关最现代实现的特征。
    • 问题是关于一个具体的实现。您只需发布另一个实现。那里——因为你甚至不试图回答这个问题。
    • @vikingosegundo 提问者问天气 GCD 是创建线程安全单例的最佳方法,我的回答给出了另一种选择。有什么问题吗?
    • 提问者询问某个实现是否是线程安全的。他不是在寻求选择。
    【解决方案8】:
    //Create Singleton  
      +( instancetype )defaultDBManager
        {
    
            static dispatch_once_t onceToken = 0;
            __strong static id _sharedObject = nil;
    
            dispatch_once(&onceToken, ^{
                _sharedObject = [[self alloc] init];
            });
    
            return _sharedObject;
        }
    
    
    //In it method
    -(instancetype)init
    {
        self = [super init];
      if(self)
         {
       //Do your custom initialization
         }
         return self;
    }
    

    【讨论】:

      【解决方案9】:

      你问这是否是“创建单例的最佳方式”。

      一些想法:

      1. 首先,是的,这是一个线程安全的解决方案。 dispatch_once 模式是在 Objective-C 中生成单例的现代、线程安全的方式。不用担心。

      2. 不过,您问这是否是“最佳”方式。不过,应该承认instancetype[[self alloc] init] 在与单例一起使用时可能会产生误导。

        instancetype 的好处在于,它是一种明确的方式,可以明确地声明类可以被子类化,而无需使用 id 的类型,就像我们在过去所做的那样。

        但是这种方法中的static 提出了子类化挑战。如果ImageCacheBlobCache 单例都是Cache 超类的子类而不实现它们自己的sharedCache 方法怎么办?

        ImageCache *imageCache = [ImageCache sharedCache];  // fine
        BlobCache *blobCache = [BlobCache sharedCache];     // error; this will return the aforementioned ImageCache!!!
        

        为此,您必须确保子类实现自己的 sharedInstance(或您为特定类调用的任何名称)方法。

        底线,您原来的sharedInstance 看起来 会支持子类,但它不会。如果您打算支持子类化,至少包括警告未来开发人员必须重写此方法的文档。

      3. 为了与 Swift 实现最佳互操作性,您可能希望将其定义为属性,而不是类方法,例如:

        @interface Foo : NSObject
        @property (class, readonly, strong) Foo *sharedFoo;
        @end
        

        然后您可以继续为此属性编写一个 getter(实现将使用您建议的 dispatch_once 模式):

        + (Foo *)sharedFoo { ... }
        

        这样做的好处是,如果 Swift 用户使用它,他们会执行以下操作:

        let foo = Foo.shared
        

        注意,没有(),因为我们将它实现为一个属性。从 Swift 3 开始,这就是通常访问单例的方式。因此,将其定义为属性有助于促进这种互操作性。

        顺便说一句,如果你看看 Apple 如何定义他们的单例,这就是他们采用的模式,例如他们的NSURLSession单例定义如下:

        @property (class, readonly, strong) NSURLSession *sharedSession;
        
      4. 另一个非常小的 Swift 互操作性考虑是单例的名称。最好能包含类型的名称,而不是 sharedInstance。例如,如果类是Foo,您可以将单例属性定义为sharedFoo。或者如果类是DatabaseManager,你可以调用属性sharedManager。然后 Swift 用户可以这样做:

        let foo = Foo.shared
        let manager = DatabaseManager.shared
        

        显然,如果您真的想使用sharedInstance,您可以随时声明 Swift 名称:

        @property (class, readonly, strong) Foo* sharedInstance NS_SWIFT_NAME(shared);
        

        显然,在编写 Objective-C 代码时,我们不应该让 Swift 互操作性超过其他设计考虑因素,但是,如果我们可以编写优雅地支持这两种语言的代码,那就更好了。

      5. 我同意其他人的观点,他们指出,如果您希望这是一个真正的单例,开发人员不能/不应该(意外)实例化他们自己的实例,unavailable 限定符 initnew 是谨慎的。

      【讨论】:

        【解决方案10】:
        @interface className : NSObject{
        +(className*)SingleTonShare;
        }
        
        @implementation className
        
        +(className*)SingleTonShare{
        
        static className* sharedObj = nil;
        static dispatch_once_t once = 0;
        dispatch_once(&once, ^{
        
        if (sharedObj == nil){
            sharedObj = [[className alloc] init];
        }
          });
             return sharedObj;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-07-24
          • 1970-01-01
          • 2012-02-03
          • 1970-01-01
          • 2016-02-04
          • 1970-01-01
          相关资源
          最近更新 更多