Отладка основных данных __NSCFSet addObject nil exception

Во время модульных тестов я получаю исключения в потоке Core Data с таким сообщением:

CoreData: ошибка: серьезная ошибка приложения. Исключение было обнаружено во время обработки изменения основных данных. Обычно это ошибка наблюдателя NSManagedObjectContextObjectsDidChangeNotification. - [__ NSCFSet addObject:]: попытка вставить nil с помощью 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

Я пытаюсь определить причину этого, но поскольку это происходит в очереди NSManagedObjectContext, у потока нет трассировки стека с каким-либо моим собственным кодом.

Я установил символические точки останова на -[__NSCFSet addObject:] и -[NSManagedObjectContext processPendingChanges], но не смог увидеть какое-либо состояние, пока останавливался на них, что помогло мне определить, какие объекты вызывают проблемы.

Следующим шагом, который пришёл мне в голову, было попробовать swizzling -[__NSCFSet addObject:], чтобы добавить мою собственную реализацию, так что я мог остановиться только тогда, когда аргумент был равен нулю. Надеюсь, набор не пустой, и я мог бы получить больше информации, просмотрев его содержимое, прежде чем вставлять nil. Однако у меня возникли трудности с его использованием, так как это частный урок.

Как я могу получить дополнительную информацию о том, что вызывает исключение, используя описанный выше подход или тот, который я не рассматривал?


person Dov    schedule 07.01.2016    source источник
comment
Похоже на проблему параллелизма. Добавьте аргумент -com.apple.CoreData.ConcurrencyDebug 1 в схему вашего проекта.   -  person bteapot    schedule 11.01.2016
comment
@bteapot Это именно то, что мне нужно. Если бы вы могли дать более развернутый ответ, я его принимаю.   -  person Dov    schedule 15.01.2016


Ответы (3)


Спасибо @bteapot за предложение добавить аргумент -com.apple.CoreData.ConcurrencyDebug 1 в мою схему. Дополнительную информацию о том, как это работает, я получил из превосходной Core Data Concurrency Debugging от Оле Бегеманна. статья.

Добавление этого флага вызывает исключение, как только ваш код вызывает NSManagedObjectContext из неправильного потока. Это отлично работает в Xcode, но имейте в виду, что в Xcode Bot это приводит к сбою тестов с этим бесполезным сообщением:

Потеряно соединение со службой диспетчера тестов

person Dov    schedule 15.01.2016
comment
Большое спасибо за это! - person Kukiwon; 06.07.2017
comment
Обычно я воздерживаюсь от написания благодарностей и тому подобного, но одного голоса за эту схему недостаточно ... Вы и @bteapot - звезды ... - person Ahmed Khedr; 24.10.2017
comment
@ Дов: В чем была настоящая проблема? Используете неправильную комбинацию контекста и потока? - person shallowThought; 07.11.2017
comment
@shallowThought Да, именно так - person Dov; 15.11.2017

В быстром 5

Создать контейнер сохраняемости

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
    }()
}

Создан ManagedContext с фоновым параллелизмом

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

Применить 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))
        }
        }
    }
person Wasim    schedule 23.07.2019

У меня была такая же проблема, и я боролся с ней сегодня весь день. Добавление отладки параллелизма было для меня шагом в правильном направлении, но основная проблема заключалась в том, что я пытался получить доступ к одному и тому же экземпляру контекста управляемого объекта из разных потоков. Метод, вызвавший мой сбой, пытался вернуть извлеченный объект. Я смог исправить это, заключив мой блок do, try, catch в блок context.perform и заставив метод принимать экранирующее закрытие в качестве параметра вместо того, чтобы возвращать объект.

func fetchUser(id: Int, completion: @escaping (User) -> ()) {
    let predicate = NSPredicate(format: "id = %d", id)
    let request: NSFetchRequest<User> = User.fetchRequest()
    request.predicate = predicate
    context.perform {
        // try to fetch user from core data
        do {
            if let user = try self.context.fetch(request).first {
                completion(user)
            } else {
                // if no user exists with that id, create and return a new user
                self.seedAutoPopulatingLists(for: id)
                completion(NSEntityDescription.insertNewObject(forEntityName: "User", into: self.context) as! User)
            }
        } catch let error as NSError {
            print("Could not fetch: \(error) \(error.userInfo)")
        }
    }
}

Надеюсь, это поможет любому, кто столкнется с этим в будущем.

person Sean Calkins    schedule 31.01.2018