1

I'm getting a non-reproduceable crash and I'm not sure why. I'm caching images on a background queue. The image names are properties on a Core Data NSManagedObject subclass, CCCard. At the same time, I have a collection view that is also accessing these CCCards.

Here is the relevant code and notes to follow.

//CCDeckViewController.m --------------------------------------

- (void)viewDidLoad {
    // This will fetch the CCCard objects from Core Data.
    self.cards = [[CCDataManager shared] cards];

    [self cacheImages];
}

- (void)cacheCardImages {
    // Because these are NSManagedObjects, I access them in the background
    // via their object ID. So pull the IDs out here.
    NSArray *cardIds = [self.cards valueForKey:@"objectID"];

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        for (NSManagedObjectID *cardId in cardIds) {

            // Fetch the actual CCCard object.
            CCCard *card = (CCCard *)[[CCDataManager shared] objectWithId:cardId];

            // Cache the card's image if it's not already there.
            NSString *key = [card thumbnailCacheKey];
            if ([self.cardImageCache objectForKey:key]) {
                continue;
            }
            CCDiscardableImage *discardable = [CCHelper decompressedImageForPath:key size:[card thumbnailSize] tintable:[card tintable]];
            [self.cardImageCache setObject:discardable forKey:key];
        }
    });
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    CCCard *card = self.cards[indexPath.item];

    // This line calls the code that crashes.
    UIColor *color = [card color];

    // More code that returns the cell.
}


// CCCard.m --------------------------------------

- (UIColor *)color {
    // Crash happens on this line.
    if (self.colorId.integerValue == 0) {
        // some code
    }
}

Here are parts of the stack trace from the Crashlytics report I think are most relevant:

Thread #0: Crashed: com.apple.main-thread
EXC_BREAKPOINT 0x000000000000defe
0  CoreData                       0x24c1b070 _sharedIMPL_pvfk_core + 247
1  CoreData                       0x24c1b071 _sharedIMPL_pvfk_core + 248
2  myapp                          0x4286f -[CCCard color] (CCCard.m:37)
3  myapp                          0x40bbb -[CCDeckViewController collectionView:cellForItemAtIndexPath:] (CCDeckViewController.m:127)

Thread #4: com.apple.root.utility-qos
0  libsystem_pthread.dylib        0x2321920c RWLOCK_GETSEQ_ADDR
1  libsystem_pthread.dylib        0x23219295 pthread_rwlock_unlock + 60
2  libobjc.A.dylib                0x22cb8e01 rwlock_tt<false>::unlockRead() + 8
3  libobjc.A.dylib                0x22cb5af5 lookUpImpOrForward + 488
4  libobjc.A.dylib                0x22cb5903 _class_lookupMethodAndLoadCache3 + 34
5  libobjc.A.dylib                0x22cbbd7b _objc_msgSend_uncached + 26
6  CoreData                       0x24c1d3ab _PFObjectIDFastEquals64 + 38
7  CoreFoundation                 0x233eeed4 CFBasicHashFindBucket + 1820
8  CoreFoundation                 0x233ee775 CFDictionaryGetValue + 116
9  CoreData                       0x24c17037 _PFCMT_GetValue + 122
10 CoreData                       0x24c16ec7 -[NSManagedObjectContext(_NSInternalAdditions) _retainedObjectWithID:optionalHandler:withInlineStorage:] + 58
11 CoreData                       0x24c1b45d _PF_FulfillDeferredFault + 940
12 CoreData                       0x24c1afcf _sharedIMPL_pvfk_core + 86
13 myapp                          0x42991 -[CCCard imagePath] (CCCard.m:53)
14 myapp                          0x41d5b __39-[CCDeckViewController cacheCardImages]_block_invoke (CCDeckViewController.m:295)

It's frustrating because it never happens during development, so can't test any theories. I'm trying to understand the code and crash report to figure out the problem now. My guess is that it has something to do with faulting because it accesses the CCCard object's instance method, but then crashes when trying to read the property from it. But why?

abc123
  • 8,043
  • 7
  • 49
  • 80

1 Answers1

5

You are violating thread confinement.

Per the Core Data documentation, a NSManagedObjectContext and any NSManagedObject associated with it must only be accessed on the queue that it is assigned to.

Your dispatch_async violates that rule and needs to be corrected. There is no longer a design where you can use Core Data in a dispatch_async like that.

If you want the processing to be asynchronous then you should spawn a private NSManagedObjectContext that is a child of your main NSManagedObjectContext and then execute a -performBlock: against it. That will give you background processing that is configured properly.

Also, I highly recommend turning on -com.apple.CoreData.ConcurrencyDebug 1 as that will catch issues like this during development.

Marcus S. Zarra
  • 46,571
  • 9
  • 101
  • 182
  • Thanks for the answer Marcus. Is there any way to determine that it is indeed the thread confinement violation that is causing the crash? I have about 5 tables in a single data store and a crash always happens when we are trying to save one specific table's changes. I know there are a lot of concurrency violations in my app, but I have no way of "proving" that these violations are causing the crash for me to push for a fix. would really appreciate your thoughts? BTW, Looking forward for your new book – shrutim Apr 11 '16 at 22:25
  • 1
    As I said in the last sentence: turn on the currency debug flag: `-com.apple.CoreData.ConcurrencyDebug 1` and you will know for **certain** if you have threading issues. – Marcus S. Zarra Apr 11 '16 at 22:47
  • Thanks.. I did that and I know I have threading issues..Given that I have threading issues, is the following statement correct? "The random crashes happening in ManagedObjectContext.save() ARE due to threading issues" – shrutim Apr 11 '16 at 22:54
  • No that is not correct. If you are getting exceptions that are triggering breakpoints in the save, that is merge conflicts and not actual crashes. Just resume. If you are getting actual crashes, then read the crash, it will tell you if it is a threading crash. – Marcus S. Zarra Apr 12 '16 at 03:53