场景:UINavigationViewController包含UICollectionViewControllers。每个集合视图都需要从JSON API获取其数据,这可能需要一些时间。然后,集合视图中的每个单元都会将自己的请求发送到服务器,从而获取缩略图。 (为清楚起见,我创建了一个sequence diagram以显示我是如何做到这一点的,不包括我已禁用调试的缩略图请求。
这就是代码的样子:
-(void)buildDisplayItems
{
NSLog(@"Collection VC (object: %@) with collection (object: %@) building display items on thread %@ (main thread: %c)", self, self.showingCollection, [NSThread currentThread], [NSThread isMainThread]);
[self.showingCollection buildDisplayItemsWithPhotoBatch:self.nextBatch++]; // <-- this waits for the server request to be made, come back and get processed into arrays before returning.
self.photos = self.showingCollection.photos;
self.collections = self.showingCollection.subcategories;
// Past this point, I cannot think of how these arrays would possibly get changed to trigger the 'mutated while being enumerated' errors.
[self.collectionView reloadData];
}
问题是如果用户在JUST THE RIGHT TIMES导航回导航视图,我会收到以下错误之一:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <NSConcreteMapTable: 0x1f047c80> was mutated while being enumerated.'
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSSetM: 0x203f26b0> was mutated while being enumerated.'
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[3]'
要发生这些错误,我必须尝试3-4次选择一些项目,以便将其集合视图推到导航堆栈上,然后快速按下后退按钮。显然,我对前两个错误的第一印象是数据源数组正在以某种方式进行修改,但我没有做任何事情就是这样做。然后我想也许是因为在cellForItemAtIndexPath返回它之后,每个单元格的缩略图图像都被加载了,所以我禁用了它,但没有任何改变。
我在周围散布了一些NSLog,发现当集合视图调用其委托方法时会发生这种情况。我确保添加以下内容:
-(void)viewWillDisappear:(BOOL)animated
{
if (self.showingCollection != nil) {
[self.showingCollection cancelOperations]; //for network requests
NSLog(@"Collection VC (object: %@) will disappear", self);
}
}
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.collectionView.delegate = nil;
self.collectionView.dataSource = nil;
self.collectionView = nil;
}
但这并没有帮助阻止错误。实际上,有时候崩溃发生时,没有“收集VC会消失”的日志消息。
另一个问题是这些错误发生并且调试器在UIApplicationMain行的main函数中停止(这是'所有异常中断'断点),所以我无法确定错误是如何被触发的。我已经启用了僵尸,但是没有来自它们的日志消息。 (编辑:有时它实际上在空线程中停止,说“错误:地址不包含指向目标文件中某个部分的部分”)
如何设置这些集合视图,以便快速按下后退按钮不会结束这个世界?
修改 如何在枚举时找出正在更改的集合?
更新 我发现这个SO线程谈论的是无法获得堆栈跟踪。每次我得到上述例外情况之一时,它会给我一个像
这样的堆栈跟踪*** First throw call stack:
(0x327453e7 0x3a440963 0x32744ec1 0x330f72c7 0x330f769b 0x330f825b 0x330fa0c7 0x330faefd 0x33103cab 0x3498c519 0x3498d5f1 0x3498d915 0x349920d9 0x34991fe3 0x3268bacd 0x34991f97 0x3268bacd 0x34991f97 0x3268bacd 0x34991f97 0x3268bacd 0x34991f97 0x3268bacd 0x34991f97 0x3268bacd 0x34991f97 0x330fa997 0x3498bf3d 0x345c73dd 0x34303513 0x343030b5 0x34303fd9 0x343039c3 0x343037d5 0x3434a567 0x3a87febb 0x3a87fb93 0x3a898fb8 0x32fe5fc7 0x3305d24f 0x3a88d0e1 0x3a88cfa8)
libc++abi.dylib: terminate called throwing an exception
根据该线程的建议,我实现了自己的未捕获异常处理程序,并打印出以下堆栈跟踪:
0 CoreFoundation 0x327453ff <redacted> + 186
1 libobjc.A.dylib 0x3a440963 objc_exception_throw + 30
2 CoreFoundation 0x32744ec1 <redacted> + 0
3 Foundation 0x330f72c7 <redacted> + 422
4 Foundation 0x330f769b <redacted> + 298
5 Foundation 0x330f825b <redacted> + 202
6 Foundation 0x330fa0c7 <redacted> + 242
7 Foundation 0x330faefd <redacted> + 500
8 Foundation 0x33103cab <redacted> + 390
9 UIKit 0x3498c519 <redacted> + 128
10 UIKit 0x3498d5f1 <redacted> + 196
11 UIKit 0x3498d915 <redacted> + 88
12 UIKit 0x349920d9 <redacted> + 84
13 UIKit 0x34991fe3 <redacted> + 182
14 CoreFoundation 0x3268bacd CFArrayApplyFunction + 176
15 UIKit 0x34991f97 <redacted> + 106
16 CoreFoundation 0x3268bacd CFArrayApplyFunction + 176
17 UIKit 0x34991f97 <redacted> + 106
18 CoreFoundation 0x3268bacd CFArrayApplyFunction + 176
19 UIKit 0x34991f97 <redacted> + 106
20 CoreFoundation 0x3268bacd CFArrayApplyFunction + 176
21 UIKit 0x34991f97 <redacted> + 106
22 CoreFoundation 0x3268bacd CFArrayApplyFunction + 176
23 UIKit 0x34991f97 <redacted> + 106
24 CoreFoundation 0x3268bacd CFArrayApplyFunction + 176
25 UIKit 0x34991f97 <redacted> + 106
26 Foundation 0x330fa997 <redacted> + 166
27 UIKit 0x3498bf3d <redacted> + 124
28 UIKit 0x345c73dd <redacted> + 72
29 QuartzCore 0x34303513 <redacted> + 214
30 QuartzCore 0x343030b5 <redacted> + 460
31 QuartzCore 0x34303fd9 <redacted> + 16
32 QuartzCore 0x343039c3 <redacted> + 238
33 QuartzCore 0x343037d5 <redacted> + 316
34 QuartzCore 0x3434a567 <redacted> + 170
35 libsystem_c.dylib 0x3a87febb _pthread_tsd_cleanup + 174
36 libsystem_c.dylib 0x3a87fb93 <redacted> + 118
37 libsystem_c.dylib 0x3a898fb8 pthread_exit + 27
38 Foundation 0x32fe5fc7 <redacted> + 10
39 Foundation 0x3305d24f <redacted> + 1002
40 libsystem_c.dylib 0x3a88d0e1 <redacted> + 308
41 libsystem_c.dylib 0x3a88cfa8 thread_start + 8
答案 0 :(得分:1)
1:当您使用集合视图离开视图控制器时,您是否在集合视图中使用委托引用和数据源,以便不再查询?
对于表视图也会发生这种情况,对象仍然存在,但数据或源不会。
2:我还建议在修改集合时使用@synchronized(self)。因此设置和读取它的代码不会发生冲突(枚举崩溃)