2013-01-07 102 views
2

为了使用全局变量和方法,我实现了Singleton作为健康的编码实践。在执行之前,我遵循了Apple documents,john wordsworth blog。首先,我没有让我的单例线程安全,我实现了这个方法以及博客和Apple文档中提到的所有其他方法。使用线程安全的代码执行停止单例初始化代码

+ (SingletonClass *)sharedManager 
{ 
    static SingletonClass *sharedManager = nil; 
    if (sharedManager == nil) { 
    sharedManager = [[super allocWithZone:NULL] init]; 
} 
    return sharedManager; 
} 

之后,为了使辛格尔顿线程安全我犯了这样的变化+ (SingletonClass *)sharedManager类和我的应用程序停止发射。我把断点,并观察dispatch_once被调用两次,然后代码停止执行进一步。

+(SingletonClass *)sharedManager 
{ 
    static SingletonClass *sharedManager = nil; 
    if (sharedManager !=nil) 
    { 
    return sharedManager; 
    } 
    static dispatch_once_t pred;  
    dispatch_once(&pred, ^{ 
    sharedManager = [SingletonClass alloc]; 
    sharedManager=[sharedManager init]; 
}); 

    return sharedManager; 
} 

如果我删除这个线程安全代码片段并恢复到以前的代码它工作正常,代码得到执行。

请注意,我还查看了bbum's answer here,他提到问题之前提到了可能的死锁情况,但我无法弄清楚问题所在。任何解释或解决方案都会对我有所帮助。谢谢。

编辑1:

如果有人想看看完整的代码,我创建gist了点。请跟着那里。谢谢。

回答

7

让我们考虑一下,如果两个线程几乎同时调用sharedManager秒版本会发生什么。

线程1先调用。它检查sharedManager !=nil,这是错误的,所以它会继续到dispatch_once。在dispatch_once块中,它执行[SingletonClass alloc]并将结果存储在sharedManager中。

现在,在线程1继续到下一行之前,线程2出现并调用sharedManager。线程2检查sharedManager !=nil,现在是这样。所以它返回sharedManager,然后调用者尝试使用sharedManager。但目前sharedManager尚未完全初始化。那很糟。

您不能设置sharedManager,直到您有一个完全初始化对象将其设置为。另外(正如borrrden指出的那样),你不需要在顶部检查sharedManager !=nil,因为dispatch_once无论如何都是非常有效的。

+ (SingletonClass *)sharedManager { 
    static dispatch_once_t pred; 
    static SingletonClass *sharedManager; 
    dispatch_once(&pred, ^{ 
     sharedManager = [[SingletonClass alloc] init]; 
    }); 
    return sharedManager; 
} 

现在,我已经看了你的要点,你的问题是在这里:

+ (id)allocWithZone:(NSZone*)zone { 
    return [[self sharedManager] retain]; 
} 

+[SingletonClass sharedManager]方法在dispatch_once块调用+[SingletonClass alloc]。由于您不覆盖alloc,+[SingletonClass alloc]调用+[SingletonClass allocWithZone:NULL]。和+[SingletonClass allocWithZone:]方法调用+[SingletonClass sharedManager]。在对sharedManager的第二次调用中,您的程序挂起dispatch_once,因为您仍在第一次调用dispatch_once

最简单的解决方法是删除您的实施allocWithZone:。只需记录sharedManager是获得SingletonClass实例的唯一受支持方式,然后继续。

如果你想变钝,并让[[SingletonClass alloc] init]返回单身人士,即使你反复做这件事情,它很复杂。请勿尝试覆盖allocallocWithZone:。这样做:

static SingletonClass *sharedManager; // outside of any method 

+ (SingletonClass *)sharedManager { 
    return sharedManager ? sharedManager : [[SingletonClass alloc] init]; 
} 

- (id)init { 
    static dispatch_once_t once; 
    dispatch_once(&once, ^{ 
     if (self = [super init]) { 
      // initialization here... 
      sharedManager = self; 
     } 
    }); 
    self = sharedManager; 
    return self; 
} 
+0

感谢您的代码的详细解释..我写了'sharedManager = [[SingletonClass alloc] init];'在第一个然后只分成两行,每个bbum回答我已经提到过,你是正确的问题是在别的地方,如果有人想看完整的代码,也发布了完整代码的链接 –

+0

+1 from me对于很好的解释,帮助我 –

+0

我已经更新了我的答案 –

1

你不需要顶部的检查,摆脱if声明。 dispatch_once保证块只在应用程序的生命周期中执行一次,因此第一次检查是多余的。

更多信息: http://cocoasamurai.blogspot.jp/2011/04/singletons-your-doing-them-wrong.html

+0

它没有帮助..:/ –

+0

值得添加:dispatch_once是快速的。至少与“无”检查一样快。不同的是,有人写了一些非常聪明的代码,以使其线程安全,而“无”的检查则不是。在最糟糕的情况下,sharedSingleton可能会返回nil,并且代码被写入(如果第二次调用进来,而sharedSingleton在分配之前仍然为零,则正在运行的dispatch_once完成,dispatch_once不执行任何操作,因为第一个dispatch_once已完成,并且因为sharedSingleton编译器认为它仍然是零,并返回nil – gnasher729