我对Apple的以下代码示例的线程安全性有疑问(来自GameKit编程指南)
这是从游戏中心加载成就并在本地保存:
步骤1)将一个可变字典属性添加到报告成就的类中。这本词典存储了成就对象的集合。
@property(nonatomic, retain) NSMutableDictionary *achievementsDictionary;
步骤2)初始化成就词典。
achievementsDictionary = [[NSMutableDictionary alloc] init];
步骤3)修改加载加载成就数据的代码,将成就对象添加到字典中。
{
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error)
{
if (error == nil)
{
for (GKAchievement* achievement in achievements)
[achievementsDictionary setObject: achievement forKey: achievement.identifier];
}
}];
我的问题如下 - achievementDictionary对象正在完成处理程序中被修改,没有任何排序锁。这是否允许,因为完成处理程序是一个工作块,iOS将保证在主线程上作为单元执行?并且永远不会遇到线程安全问题?
在另一个Apple示例代码(GKTapper)中,此部分的处理方式不同:
@property (retain) NSMutableDictionary* earnedAchievementCache; // note this is atomic
然后在处理程序中:
[GKAchievement loadAchievementsWithCompletionHandler: ^(NSArray *scores, NSError *error)
{
if(error == NULL)
{
NSMutableDictionary* tempCache= [NSMutableDictionary dictionaryWithCapacity: [scores count]];
for (GKAchievement* score in scores)
{
[tempCache setObject: score forKey: score.identifier];
}
self.earnedAchievementCache= tempCache;
}
}];
那么为什么不同的风格,并且比另一种更正确?
答案 0 :(得分:2)
这是否允许,因为完成处理程序是一个工作块,iOS将保证在主线程上作为单元执行?并且永远不会遇到线程安全问题?
这绝对不是这种情况。 -loadAchievementsWithCompletionHandler:
的文档明确警告可能会在您开始加载的线程之外的线程上调用完成处理程序。
Apple的“线程编程指南”在线程不安全的类中对NSMutableDictionary
进行了分类,但对此进行了限定,“在大多数情况下,只要您在一个线程中使用这些类,就可以使用这些类。一时间。“
因此,如果两个应用程序都设计为在完成工作任务更新之前不会使用成就缓存,则不需要同步。这是我可以看到第一个例子是安全的唯一方法,而且这是一个很小的安全。
后一个示例看起来依赖于原子属性支持来在旧缓存和新缓存之间进行切换。这应该是安全的,前提是所有对财产的访问都是通过其访问者而不是直接进行ivar访问。这是因为访问器彼此同步,因此您不会冒险看到半设置值。此外,getter保留并自动释放返回的值,因此旧版本的代码将能够完成它的使用而不会崩溃,因为它是在其工作中发布的。非原子getter只是直接返回对象,这意味着如果另一个线程为该属性设置了一个新值,它可以从你的代码下解除分配。直接ivar访问可能会遇到同样的问题。
我想说后一个例子既正确又优雅,但如果没有评论解释该财产的原子性至关重要,可能有点过于微妙。