iOS CoreData batch insert?
Swift 5.x
In 2022, consider using NSBatchInsertRequest
to insert batch data into persist store.
⚠️ Warning: NSBatchRequest will not post Notification. If you using observer to detect data change, you must handle batch request manual.
Create Batch Insert Request
Assume CustomMO
is target NSManagedObject, and here's code sample
// Load all data from JSON file
var jsonData: [CustomObjectData] = loadData()
func createBatchInsertRequest() -> NSBatchInsertRequest {
// Create an iterator for raw data
var itemListIterator = jsonData.makeIterator()
let batchInserRequest = NSBatchInsertRequest(entity: CustomMO.entity()) { (obj: NSManagedObject) in
// Stop add item when itemListIterator return nil
guard let item = itemListIterator.next() else { return true }
// Convert obj to CustomMO type and fill data to obj
if let cmo = obj as? CustomMO {
cmo.name = item.name
cmo.description = item.description
}
// Continue add item to batch insert request
return false
}
return batchInserRequest
}
Execute Request
You can choose directly execute request or run it in background
var container: NSPersistentContainer!
let request = createBatchInsertRequest()
// Directly Execute
do {
try container.viewContext.execute(request)
} catch {
// Log Error Here
}
// Execute In background
container.performBackgroundTask { context in
do {
try context.execute(request)
} catch {
// Log Error Here
}
}
Extra Steps Refresh Row Cache
The code below from here
request.resultType = .objectIDs
let result = try! context.execute(request)
let resultInsert = result as? NSBatchInsertResult
if let objectIDs = resultInsert?.result as? [NSManagedObjectID], !objectIDs.isEmpty {
let save = [NSInsertedObjectsKey: objectIDs]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: save, into: [mainContext])
}
Check out the Efficiently Importing Data chapter from the Core Data Programming Guide.
I'm currently having the same problems as you, only I'm inserting 10000 objects and it takes around 30s, which is still slow for me. I'm doing a [managedObjectContext save] on every 1000 managed objects inserted into the context (in other words, my batch size is 1000). I've experimented with 30 different batch sizes (from 1 to 10000), and 1000 seems to be the optimum value in my case.
I was looking for the answer to a similar question when I came across this one. @VladimirMitrovic's answer was helpful at the time for knowing that I shouldn't save the context every time, but I was also looking for some sample code.
Now that I have it, I will provide the code below so that other people can see what it might look like to do a batch insert.
// set up a managed object context just for the insert. This is in addition to the managed object context you may have in your App Delegate.
let managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = (UIApplication.sharedApplication().delegate as! AppDelegate).persistentStoreCoordinator // or wherever your coordinator is
managedObjectContext.performBlock { // runs asynchronously
while(true) { // loop through each batch of inserts. Your implementation may vary.
autoreleasepool { // auto release objects after the batch save
let array: Array<MyManagedObject>? = getNextBatchOfObjects() // The MyManagedObject class is your entity class, probably named the same as MyEntity
if array == nil { break } // there are no more objects to insert so stop looping through the batches
// insert new entity object
for item in array! {
let newEntityObject = NSEntityDescription.insertNewObjectForEntityForName("MyEntity", inManagedObjectContext: managedObjectContext) as! MyManagedObject
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
}
// only save once per batch insert
do {
try managedObjectContext.save()
} catch {
print(error)
}
managedObjectContext.reset()
}
}