Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debugging Core Data __NSCFSet addObject nil exception

I'm getting exceptions thrown during my unit tests, on a Core Data thread with this message:

CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'
*** First throw call stack:
(
    0   CoreFoundation                      0x00683a14 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x02334e02 objc_exception_throw + 50
    2   CoreFoundation                      0x0068393d +[NSException raise:format:] + 141
    3   CoreFoundation                      0x005595b9 -[__NSCFSet addObject:] + 185
    4   CoreData                            0x001d47c0 -[NSManagedObjectContext(_NSInternalChangeProcessing) _processPendingInsertions:withDeletions:withUpdates:] + 560
    5   CoreData                            0x001cee8a -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 2410
    6   CoreData                            0x001ce506 -[NSManagedObjectContext processPendingChanges] + 54
    7   CoreData                            0x001f359b developerSubmittedBlockToNSManagedObjectContextPerform + 443

I'm trying to determine what's causing it, but since it's occurring on an NSManagedObjectContext queue, the thread has no stack trace with any of my own code.

I set symbolic breakpoints on -[__NSCFSet addObject:] and -[NSManagedObjectContext processPendingChanges], but wasn't able to see any state while stopped there, helping me determine which objects are causing issues.

The next step that occurred to me was to try swizzling -[__NSCFSet addObject:] to add my own implementation, so I could stop only when the argument is nil. Hopefully the set isn't empty, and I could get more information by seeing its contents before inserting nil. I ran into difficulties swizzling it, though, since it's a private class.

Using an approach above, or one I hadn't considered, how can I get more information on what's causing the exception?

like image 398
Dov Avatar asked Jan 07 '16 15:01

Dov


2 Answers

Thanks to @bteapot for suggesting I add the -com.apple.CoreData.ConcurrencyDebug 1 argument to my scheme. I got more information on how this works from Ole Begemann's excellent Core Data Concurrency Debugging article.

Adding this flag causes an exception to be thrown as soon as your code calls into your NSManagedObjectContext from an incorrect thread. This works great in Xcode, but be advised that in an Xcode Bot, this causes tests to fail with this unhelpful message:

Lost connection to test manager service

like image 56
Dov Avatar answered Sep 17 '22 16:09

Dov


In swift 5

Create Persistence container

private final class PersistanceContainerProvider  {
    var container: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Test")
        container.loadPersistentStores(completionHandler: { storeDescription, error in
            if let error = error as NSError? {
                fatalError("Unable to load persistance store")
            }
        })
        return container
    }()
}

Created ManagedContext with background concurrency

private init(persistentContainerProvider: DefaultPersistanceContainerProvider = PersistanceContainerProvider()){
        self.persistentContainer = persistentContainerProvider.container
        self.managedObjectContext = persistentContainer.newBackgroundContext()
        print("Wasim \(self.managedObjectContext.concurrencyType.rawValue)")
    }

Apply managedObjectContext.performAndWait

func addTargetCode(symbol: Symbol) {
        managedObjectContext.performAndWait {
        do {
            if let existing = try managedObjectContext.fetch(ManagedTargetCurrency.fetchRequest(by: symbol.code)).first {
                existing.code = symbol.code
                existing.name = symbol.name
                 try save()
            } else {
                let added = (NSEntityDescription.insertNewObject(forEntityName: ManagedTargetCurrency.entityName, into: managedObjectContext) as? ManagedTargetCurrency).require(hint: "Wrong Core Data Configuration?")
                added.code = symbol.code
                added.name = symbol.name
                 try save()
            }
        } catch let error {
            print(Errors.addError(cause: error))
        }
        }
    }
like image 33
Wasim Avatar answered Sep 19 '22 16:09

Wasim