Coredata is very slow; swift

If you have many objects of the same type, Core Data is naturally going to take a long time to save things. Doesn't matter what you do, it will take a while. What you can do is to configure the save to take places in a background thread as to not freeze your UI.

The easiest way is to create a background context for your main context. The idea is that your main context saves its data to the parent context, and the parent context is in charge of persisting data to disk. Basically like this:

Core Data Parent and Child Contexts

Because both your main context and parent context are running in memory, the save operation from the child to the parent is fast. Once the parent has your location objects, it will save in a background thread. It may still take a long time, but at the very least it should not freeze your UI.

And you can configure this in your code like this:

lazy var parentContext: NSManagedObjectContext = {
    let moc = NSManagedObjectContext(concurrencyType:.PrivateQueueConcurrencyType)
    moc.persistentStoreCoordinator = self.coordinator
    return moc
}()

lazy var context: NSManagedObjectContext = {
    let moc = NSManagedObjectContext(concurrencyType:.MainQueueConcurrencyType)
    // moc.persistentStoreCoordinator = self.coordinator
    moc.parentContext = self.parentContext
    return moc
}()

context will be the child context. You can see how easy it is to give it a parent context.

Then, to save your data:

class func save(moc:NSManagedObjectContext) {

    moc.performBlockAndWait {

        if moc.hasChanges {

            do {
                try moc.save()
            } catch {
                print("ERROR saving context \(moc.description) - \(error)")
            }
        }

        if let parentContext = moc.parentContext {
            save(parentContext)
        }
    }
}

(Code taken and slightly edited from the "Learning Core Data for iOS with Swift: A Hands-on Guide" book by Tim Roadley).


Updated for 2020...

Basically you pretty much have to use the "alternate context" which is nowadays provided by core data.

So core.container is "your" container ... very likely from your core data stack singleton.

So new data arrives for your birds of the world database ...

let pm = core.container.newBackgroundContext()
pm.perform {
    for oneBudgie in someNewData {
        ... create your new CDBudgie entity ...
        ... take EXTREME care to use "pm" throughout ...
        ... take EXTREME care to NEVER use .viewContext throughout ...
    }
}

During the loop,

... exercise extreme caution that you do not use .viewContext inside that loop. Use only "pm" inside the loop. Be especially careful if you call other code.

Say for example you have a utility routine which "finds a certain item".

Or perhaps a utility routine which simply grabs the main user.

It's possible those utility routines simply use your usual main context, core.container.viewContext .

When you write such utility routines, it's natural to just use the main context, without thinking about it.

But. It's very easy to accidentally use such a utility routine, during your code, inside the loop.

If you do that, the app will crash instantly.

You must not accidentally use core.container.viewContext somewhere deep during the code inside the loop.

You can ONLY use "pm" for anything CoreData, within the loop or from any code whatsoever which is ultimately called, no matter how deeply nested, from within that loop.

After all the new items are created, it seems you do precisely this:

func bake() {
    self.performAndWait {
        if self.hasChanges {
            do {
                try self.save()
            }
            catch { print("bake disaster type 1 \(error)") }
        }

        // BUT SEE NOTES BELOW
        if core.container.viewContext.hasChanges {
            do {
                try core.container.viewContext.save()
            }
            catch { print("bake disaster type 2 \(error)") }
        }
        // BUT SEE NOTES BELOW
 
    }
}

As far as I know. Nobody really knows, but that's as far as I know.

Hence,

let pm = core.container.newBackgroundContext()
pm.perform {
    for oneBudgie in someNewData {
        ... create your new CDBudgie entity ...
        ... be absolutely certain to only use 'pm' ...
    }
    pm.bake()
}

However in practice today you need do NOTHING with the main context:

All the many examples of "...and then save the main context" are basically wrong today.

In reality today in 99.9999% of case you will be using .automaticallyMergesChangesFromParent = true which now works perfectly and smoothly. (Example of how to do that.)

In the bake() example above, if you literally add a couple of print lines to check what happens in the second save, you will see that ...

...there is NEVER anything to be saved back on the main context!

Thus in practice your bake routine is this simple,

func bake() {
    self.performAndWait {
        if self.hasChanges {
            do {
                try self.save()
            }
            catch { print("bake disaster type 1 \(error)") }
        }
}

And a final point...

Notice that the overall bake is actually called inside a performAndWait. But, the bake itself does the important save, inside a performAndWait.

I do know that it works extremely reliably that way.

The (very few people who discuss the issue) suggest the inner one is needed.

However: it seems to me that you do not need the inner performAndWait.

So conceivably bake is even simpler -

func bake() {
    if self.hasChanges {
        do {
            try self.save()
        }
        catch {
            print("woe \(error)")
        }
    }
}

When I try that form of bake, it does seem to work well with no problems. But then, with Core Data you have to test for, well, years to find problems.

As I mention in a comment, I think there's about 2 places on the internet where this is explained, @AndyIbanez original answer above, and this updated version!