Я пытаюсь импортировать большой набор данных примерно из 80 тыс. Объектов. Я пытаюсь следовать примеру Apple
У меня две проблемы:
- В примере кода есть комментарий:
// taskContext.performAndWait runs on the URLSession's delegate queue
// so it won’t block the main thread.
Но в моем случае я не использую URLSession для получения JSON. Файл идет в комплекте с приложением. В этом случае, как убедиться, что импорт не заблокирует основной поток. Должен ли я создавать собственную очередь? Любой пример?
В этом примере просто импортируется массив сущностей. Но в моем случае мне нужно импортировать только одну сущность, у которой есть объект 70k по отношению ко многим.
Итак, я хочу достичь:
- Если есть
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