Reputation: 2213
Scenario: UINavigationViewController
contains UICollectionViewControllers
. Each collection view needs to get its data from a JSON API, which can take a bit of time. Then each cell in the collection view sends its own requests to the server, getting thumbnail images. (For clarity, I created a sequence diagram to show how I am doing this, not including the thumbnail requests, which I have disabled for debugging):
This is what the code looks like:
-(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];
}
The problem is if the user navigates back on the navigation view at JUST THE RIGHT TIMES, I get one of the following errors:
*** 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]'
To make these errors happen, I have to try 3-4 times selecting some item so its collection view gets pushed on the nav stack then quickly pressing the back button. Obviously, my first impression from the first two errors was that the data source arrays are getting modified somehow, but there's NO WAY anything I wrote is doing that. Then I thought maybe the fact that the thumbnail images for each cell is getting loaded after cellForItemAtIndexPath
returns it is the problem, so I disabled that, but nothing changed.
I sprinkled some NSLogs around and found that this is happening while the collection view is making calls to its delegate methods. I made sure to add the following:
-(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;
}
But this hasn't helped in stopping the errors. In fact, some times when the crash happens, there is no "Collection VC will disappear" log message.
The other problem is that these errors occur and the debugger stops in the main function at the UIApplicationMain line
(this is with 'break on all exceptions' breakpoint), so I have no way of pinpointing how the error gets triggered. I have enabled zombies but there's no log message from them. (EDIT: Sometimes it actually stops in an empty thread, saying "error: address doesn't contain a section that points to a section in a object file")
How do I set up these collection views so that pressing the back button quickly doesn't end the world?
EDIT How can I find out what is the collection that is being changed while being enumerated?
UPDATE I found this Stack Overflow thread talking about not being able to get stack traces. Every time I got one of the exceptions above, it'd give me a stack trace like
*** 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
As per that thread's suggestion I implemented my own uncaught exception handler and it prints out the following stack trace:
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
Upvotes: 1
Views: 903
Reputation: 2809
1: When you navigate away from the view controller with the collection view, are you nil'ing the delegate references and data sources in the collection view so you no longer get queried?
This happens with Table Views also, the objects still persist but the data or sources do not.
2: I also recommend using @synchronized(self) when modifying the collections. So that code that sets and reads it does not clash (the enumerated crash)
Upvotes: 1