Shinigami
Shinigami

Reputation: 2213

App Crashes When Navigating Away from Loading Collection View

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):

sequence diagram

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

Answers (1)

Mitchell Currie
Mitchell Currie

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

Related Questions