16

Apple 的多线程文档没有NSIndexPath列为线程安全的!作为一个不可变的类,我通常希望它是线程安全的。

以前,我确信用于说明NSIndexPath实例是共享且全局唯一的文档。不过现在这似乎已经消失了,这让我怀疑设计是针对 iOS5 / Mac OS X 10.7 进行的修改。

我在 Mac OS X 10.6 (Snow Leopard) 上看到了很多来自客户的崩溃报告,这些报告似乎在尝试访问索引路径时崩溃了。因此我想知道:实际实例线程安全吗,但是将它们从共享缓存中拉出的逻辑不是吗?有没有人有任何见识?

这是一个示例堆栈跟踪顺便说一句:

Dispatch queue: com.apple.root.default-priority
0 libobjc.A.dylib 0x96513f29 _cache_getImp + 9
1 libobjc.A.dylib 0x965158f0 class_respondsToSelector + 59
2 com.apple.CoreFoundation 0x948bcb49 ___forwarding___ + 761
3 com.apple.CoreFoundation 0x948bc7d2 _CF_forwarding_prep_0 + 50
4 com.apple.Foundation 0x994b10c5 -[NSIndexPath compare:] + 93
5 com.apple.Foundation 0x99415686 _NSCompareObject + 76
6 com.apple.CoreFoundation 0x948af61c __CFSimpleMergeSort + 236
7 com.apple.CoreFoundation 0x948af576 __CFSimpleMergeSort + 70
8 com.apple.CoreFoundation 0x948af38c CFSortIndexes + 252
9 com.apple.CoreFoundation 0x948fe80d CFMergeSortArray + 125
10 com.apple.Foundation 0x994153d3 _sortedObjectsUsingDescriptors + 639
11 com.apple.Foundation 0x994150d8 -[NSArray(NSKeyValueSorting) sortedArrayUsingDescriptors:] + 566

对我来说,这是一个NSIndexPath试图将自己与释放的实例进行比较的实例。

4

2 回答 2

4

到目前为止,我的最佳答案是我怀疑的:

从 OS X 10.7 和 iOS 5 开始,NSIndexPath是线程安全的。在此之前,实例是线程安全的,因为它们是不可变的,但现有实例的共享检索不是。

对于按需返回索引路径的方法,我这样做了:

- (NSIndexPath *)indexPath;
{
    NSIndexPath *result = … // create the path as appropriate

    return [[result retain] autorelease];
}

自从实现最后一行代码以来,我们没有更多来自索引路径的崩溃报告。

索引路径由-indexPathByAddingIndex:或创建+indexPathWithIndex:

我看到的结果让我非常确定(在 10.7/iOS5 之前)这些方法正在返回一个现有NSIndexPath实例。但是,当前线程不会以任何方式保留该实例,因此首先创建实例的线程(在我们的例子中是主线程)正在释放路径(可能通过弹出自动释放池)并为我们的工作线程留下一个悬空指针,如问题所示,使用时会崩溃。

这有点可怕,因为如果我的分析是正确的,我添加的retain/ autoreleasedance 只是用另一种不太可能的比赛条件替换了一个比赛条件。

在 10.7/iOS5 之前,我只能想到一个真正的解决方法:将所有索引路径的创建限制到主线程。如果这样的代码被大量调用,这可能会相当慢,因此可以通过维护自己的某种实例缓存以供后台线程使用来改进——以内存为代价。如果缓存保留了一条路径,那么您知道它不会被主线程释放。

于 2012-02-25T12:10:09.027 回答
1

Apple 没有明确将 NSIndexPath 列为线程安全的,但他们确实说不可变类通常是安全的,而可变类通常不是。由于 NSIndexPath 是不可变的,因此可以安全地假设它是线程安全的。

但是“线程安全”并不意味着它不会在你在另一个线程上使用它之前被一个线程释放而导致崩溃。线程安全通常只是意味着它的 mutator 方法包​​含锁定以防止由于两个线程同时设置属性而导致的故障(这就是为什么没有 mutator 方法的类通常是线程安全的,尽管惰性 getter 和共享实例也会导致问题)。

听起来您的错误更有可能是由于使用自动释放池或其他一些机制导致您的对象在您无法控制的时间被释放。您可能应该确保任何并发访问的对象都存储在长寿命类的属性中,以便您可以控制它们的生命周期。

创建一个自动释放的对象并在删除对其的所有强引用后从另一个线程访问它是一种危险的赛车游戏,无论所讨论的对象是否是“线程安全的”,都可能导致难以追踪的崩溃。

于 2012-02-21T08:20:07.297 回答