Ошибка CoreData: Неправильное использование API: Попытка сериализовать доступ к хранилищу на не принадлежащем координатору - PullRequest
0 голосов
/ 12 июня 2019

Примечание:
Этот пост не применяется, поскольку я фактически использую CoreData.
В этом посте последний ответ предлагает извлечь все элементы в новом фоновом потоке перед добавлением новых объектов, но это сделано в моем коде.
В этом посте предлагается разблокировать элемент перед сохранением его контекста, но это также сделано в моем коде.

Мое приложение использует CoreData для хранения объектов с именем shoppingItems. Я написал класс CoreDataManager, который инициализирует CoreData и имеет по существу одну функцию для перезаписи сохраненных в данный момент элементов и одну функцию для извлечения всех элементов. Обе функции работают в фоновом режиме, то есть в отдельном потоке.

Вот мой код (не относящиеся к делу части не учтены).

Я устанавливаю основные данные в главном потоке:

private lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: modelName)
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
    })
    return container
}()  

Это функция записи:

func overwriteShoppingItems(_ shoppingItems: Set<ShoppingItem>, completion: @escaping (Error?) -> Void) {
        let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        let viewContext = self.persistentContainer.viewContext
        backgroundContext.parent = viewContext
        backgroundContext.performAndWait {
            // Delete currently stored shopping items
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: CDEntityShoppingItem)
            do {
                let result = try backgroundContext.fetch(fetchRequest)
                let resultData = result as! [NSManagedObject]
                for object in resultData {
                    backgroundContext.delete(object)
                }

                if !shoppingItems.isEmpty {
                    // Save shopping items in managed context
                    let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: CDEntityShoppingItem, in: backgroundContext)!
                    for nextShoppingItem in shoppingItems {
                        let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity, insertInto: backgroundContext)
                        nextCdShoppingItem.name = nextShoppingItem.name
                    }
                }

                let saveError = self.saveManagedContext(managedContext: backgroundContext)
                completion(saveError)
            } catch let error as NSError {
                completion(error)
            }
        }
}

func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
    if !managedContext.hasChanges { return nil }
    do {
        try managedContext.save()
        return nil
    } catch let error as NSError {
        return error
    }
}

И это функция выборки:

func fetchShoppingItems(completion: @escaping (Set<ShoppingItem>?, Error?) -> Void) {
        let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        let viewContext = self.persistentContainer.viewContext
        backgroundContext.parent = viewContext
        backgroundContext.performAndWait {
            let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
            do {
                let cdShoppingItems: [CDShoppingItem] = try backgroundContext.fetch(fetchRequest)
                guard !cdShoppingItems.isEmpty else {
                    completion([], nil)
                    return
                }
                for nextCdShoppingItem in cdShoppingItems {
                }
                    completion(shoppingItems, nil) 
            } catch let error as NSError {
                completion(nil, error)
            }
        }
}  

При нормальной работе код работает.

Проблема:

Я также написал модульный тест, который пытается вызвать проблемы с многопоточностью. Этот тест использует параллельную очередь отправки:

let concurrentReadWriteQueue = DispatchQueue(label: „xxx.test_coreDataMultithreading", attributes: .concurrent)  

Таймер определяет время теста.
В схеме для испытаний я установил аргументы -com.apple.CoreData.Logging.stderr 1 и -com.apple.CoreData.ConcurrencyDebug 1.
Во время теста overwriteShoppingItems и fetchShoppingItems многократно вставляются в очередь и выполняются одновременно.
Этот модульный тест выполняет несколько операций чтения и записи, прежде чем остановиться на строке

                    let itemName = nextCdShoppingItem.name!  

потому что nextCdShoppingItem.name - это nil, что никогда не должно происходить, потому что я никогда не храню nil.

Непосредственно перед аварией регистрируется следующее:

CoreData: error:  API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600000e6c980, store PSC = 0x0)

Если я делаю только выборки или только записи, предупреждение CoreData регистрируется , а не . Таким образом, это определенно является проблемой многопоточности. Однако CoreData.ConcurrencyDebug не обнаруживает его.

Похоже, что во время операции выборки в одном потоке другой поток удаляет текущий выбранный элемент, поэтому его свойства считываются как nil. Но этого не должно происходить, потому что выборки и сохранения выполняются с backgroundContext.performAndWait, то есть поочередно. А трассировка стека показывает, что только один поток обращается к CoreData: Thread 3 Queue : NSManagedObjectContext 0x600003c8c000 (serial)

Мои вопросы:

  • Что я неправильно использую API-интерфейс CoreData и как это сделать правильно?
  • Почему элемент иногда читается как ноль?

РЕДАКТИРОВАТЬ:

Может быть, это помогает выявить проблему: когда я закомментирую backgroundContext.delete(object) в overwriteShoppingItems, ошибка больше не регистрируется, и ни один элемент не выбирается как nil.

1 Ответ

0 голосов
/ 13 июня 2019

Проблема вроде бы решена.
Очевидно, это произошло потому, что обе функции overwriteShoppingItems и fetchShoppingItems устанавливают отдельный фоновый контекст с let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) со своей собственной очередью, чтобы выборки и сохранения не сериализовались единой очередью.

Теперь я изменил свой код следующим образом:
У меня сейчас есть свойство

let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)  

, который инициализируется как

self.backgroundContext.persistentStoreCoordinator = self.persistentContainer.persistentStoreCoordinator
self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true

и выборки и сохранения выполняются с использованием

backgroundContext.perform {…}  

Теперь ошибка CoreData больше не регистрируется, и ни один элемент не выбирается как ноль.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...