Coredata 很慢;迅速

8 core-data ios swift

我制作了一个 iOS 应用程序来跟踪设备的 GPS 路线。问题是,当我跟踪超过 5 分钟时,coredata 需要很长时间才能保存。我保存了一个名为 session 的对象,并且 session 有很多位置对象。位置对象是 [ [纬度, 经度] ]

并且会话对象看起来像[名称:字符串,日期:nsdate,平均速度:双倍,高速:双倍,行驶公里数:双倍,位置:[[双,双]]]

所以......它有效,但5分钟后。我认为需要 2 或 3 分钟才能保存

And*_*nez 8

如果你有很多相同类型的对象,Core Data 自然要花很长时间来保存东西。不管你做什么,都需要一段时间。您可以做的是将保存配置为在后台线程中进行,以免冻结您的 UI。

最简单的方法是为您的主要上下文创建一个背景上下文。这个想法是您的主上下文将其数据保存到父上下文,而父上下文负责将数据持久化到磁盘。基本上是这样的:

核心数据父子上下文

因为你的主上下文和父上下文都在内存中运行,所以从子到父的保存操作很快。一旦父级拥有您的位置对象,它将保存在后台线程中。它可能仍然需要很长时间,但至少它不应该冻结您的 UI。

你可以在你的代码中配置它,如下所示:

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
}()
Run Code Online (Sandbox Code Playgroud)

context将是子上下文。你可以看到给它一个父上下文是多么容易。

然后,要保存您的数据:

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(代码取自 Tim Roadley 所著的“Learning Core Data for iOS with Swift: A Hands-on Guide”一书并稍作编辑)。


Fat*_*tie 6

2020 年更新...

基本上,您几乎必须使用如今核心数据提供的“替代上下文” 。

所以core.container是“你的”容器......很有可能从你的核心数据栈单。

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 ...
    }
}
Run Code Online (Sandbox Code Playgroud)

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
 
    }
}
Run Code Online (Sandbox Code Playgroud)

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()
}
Run Code Online (Sandbox Code Playgroud)

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)") }
        }
}
Run Code Online (Sandbox Code Playgroud)

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)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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!