Импорт большого набора в Core data с связями в пакетах - PullRequest
0 голосов
/ 28 мая 2020

Я пытаюсь импортировать большой набор данных примерно из 80 тыс. Объектов. Я пытаюсь следовать примеру Apple

У меня две проблемы:

  1. В примере кода есть комментарий:
 // taskContext.performAndWait runs on the URLSession's delegate queue
 // so it won’t block the main thread.

Но в моем случае я не использую URLSession для получения JSON. Файл идет в комплекте с приложением. В этом случае, как убедиться, что импорт не заблокирует основной поток. Должен ли я создавать собственную очередь? Любой пример?

В этом примере просто импортируется массив сущностей. Но в моем случае мне нужно импортировать только одну сущность, у которой есть объект 70k по отношению ко многим.

enter image description here

Итак, я хочу достичь:

  • Если есть ContactBook не импортируйте ничего, потому что мы уже импортировали JSON.
  • Если нет ContactBook, создайте его и импортируйте весь объект 70k Contact в отношение contacts отношения ContactBook. Это должно происходить партиями, как в примере, и не должно блокировать пользовательский интерфейс.

Что я пробовал:

private func insertContactbookIfNeeded() {
    let fetch: NSFetchRequest<Contactbook> = ContactBook.fetchRequest()
    let contactBookCount = (try? context.count(for: fetch)) ?? 0

    if contactBookCount > 0 {
        return
    }

    let contacts = Bundle.main.decode([ContactJSON].self, from: "contacts.json")

    // Process records in batches to avoid a high memory footprint.
    let batchSize = 256
    let count = contacts.count

    // Determine the total number of batches.
    var numBatches = count / batchSize
    numBatches += count % batchSize > 0 ? 1 : 0

    for batchNumber in 0 ..< numBatches {

        // Determine the range for this batch.
        let batchStart = batchNumber * batchSize
        let batchEnd = batchStart + min(batchSize, count - batchNumber * batchSize)
        let range = batchStart..<batchEnd

        // Create a batch for this range from the decoded JSON.
        let contactsBatch = Array(contacts[range])

        // Stop the entire import if any batch is unsuccessful.
        if !importOneBatch(contactsBatch) {
            assertionFailure("Could not import batch number \(batchNumber) range \(range)")
            return
        }
    }
}

private func importOneBatch(_ contactsBatch: [ContactJSON]) -> Bool {

    var success = false

    // Create a private queue context.
    let taskContext = container.newBackgroundContext()
    taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

    // NOT TRUE IN MY CASE: (Any suggestion ??)
    // taskContext.performAndWait runs on the URLSession's delegate queue
    // so it won’t block the main thread. 

    print("isMainThread: \(Thread.isMainThread)") // prints true

    taskContext.performAndWait {
        let fetchRequest: NSFetchRequest<ContactBook> = ContactBook.fetchRequest()
        fetchRequest.returnsObjectsAsFaults = true
        fetchRequest.includesSubentities = false

        let contactBookCount = (try? taskContext.count(for: fetchRequest)) ?? 0

        var contactBook: ContactBook?

        if contactBookCount > 0 {
            do {
                contactBook = try taskContext.fetch(fetchRequest).first
            } catch let error as NSError {
                assertionFailure("can't fetch the contactBook \(error)")
            }
        } else {
            contactBook = ContactBook(context: taskContext)
        }

        guard let book = contactBook else {
            assertionFailure("Could not fetch the contactBook")
            return
        }

        // Create a new record for each contact in the batch.
        for contactJSON in contactsBatch {

            // Create a Contact managed object on the private queue context.
            let contact = Contact(context: taskContext)
            // Populate the Contact's properties using the raw data.
            contact.name = contactJSON.name
            contact.subContacts = NSSet(array: contactJSON.subContacts { subC -> Contact in
                let contact = Contact(context: taskContext)
                contact.name = subC.name
            })
            book.addToContacts(contact)
        }

        // Save all insertions and deletions from the context to the store.
        if taskContext.hasChanges {
            do {
                try taskContext.save()
            } catch {
                print("Error: \(error)\nCould not save Core Data context.")
                return
            }
            // Reset the taskContext to free the cache and lower the memory footprint.
            taskContext.reset()
        }

        success = true
    }
    return success
}

Проблема в том, что это происходит очень медленно, потому что в каждом batch Я получаю книгу (которая становится все больше с каждой итерацией), чтобы иметь возможность вставлять новый пакет контактов в книгу контактов. Есть ли эффективный способ избежать загрузки книги в каждом пакете? также есть предложения сделать это быстрее? увеличить размер партии? создать фоновую очередь?

Обновление:

Я пытался создать ContactBook один раз в insertWordbookIfNeeded и передавать его importOneBatch с каждой итерацией, но получаю:

Thread 1: Exception: "Illegal attempt to establish a relationship 
'contactBook' between objects in different contexts
...