【问题标题】:CoreData app takes too long to quitCoreData 应用程序退出时间过长
【发布时间】:2016-03-29 19:37:26
【问题描述】:

我的应用在运行时可能会创建/删除数千个托管对象。我使用辅助 NSManagedObjectContexts(MOCs) 和 NSPrivateQueueConcurrencyType 和 NSOperations 来使应用程序更具响应性,并且大多数部分运行良好。但是当我按下 ⌘Q 并且如果未保存的对象数量很大时,应用程序会在窗口关闭之前挂起很长一段时间(沙滩球继续旋转......)。

如何让窗口立即消失,在 MOC 保存之前? 我尝试在AppDelegate中的applicationShouldTerminate中插入window.close(),但是没有效果。

我的删除代码没什么特别的,只是层次结构非常大。类似的东西

let items = self.items as! Set<Item>
Group.removeItems(items)
for i in items {
   self.managedObjectContext?.deleteObject(i)
}

Item 是一个分层实体。 Group 与项目具有一对多的关系。 removeItems 由 CoreData 使用 @NSManaged 生成。

非常感谢。


更新

我尝试了以下代码,保存仍然会阻塞 UI。

@IBAction func quit(sender: AnyObject) {
    NSRunningApplication.currentApplication().hide()
    NSApp.terminate(sender)
}

func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply 
{
    let op = NSBlockOperation { () -> Void in
        do {
            try self.managedObjectContext.save()
        } catch {
            print("error")
        }
        NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
            NSApp.replyToApplicationShouldTerminate(true)
        })
    }

    op.start()
    return .TerminateLater
}

当创建/删除的托管对象数量很大时,这不会使窗口首先关闭。

然后按照@bteapot 的建议,我更改为以下内容。仍然没有效果。窗口仍然不会立即关闭。

@IBAction func quit(sender: AnyObject) {
    NSRunningApplication.currentApplication().hide()
    NSApp.terminate(sender)
}

func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply {

    let op = NSBlockOperation { () -> Void in
        self.managedObjectContext.performBlock({ () -> Void in
            do {
                try self.managedObjectContext.save()
            } catch {
                print("errr")
            }
        })

        NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
            NSApp.replyToApplicationShouldTerminate(true)
        })
    }

    dispatch_async ( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    {() -> Void in
        op.start()
    })

    return .TerminateLater
}

最后我解决了这个问题,虽然 UI有时仍然被阻塞,即使使用相同的测试数据。

使用的方法可以在这里找到:https://blog.codecentric.de/en/2014/11/concurrency-coredata/、Core Data background context best practice、https://www.cocoanetics.com/2012/07/multi-context-coredata/

首先我用.PrivateQueueConcurrencyType做了一个backgroundMOC

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

然后把它变成原来的 moc。

lazy var managedObjectContext: NSManagedObjectContext = {
    var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
    // managedObjectContext.persistentStoreCoordinator = coordinator

    managedObjectContext.parentContext = self.backgroundMOC
    managedObjectContext.undoManager = nil

    return managedObjectContext
}()

两种保存方法。

func saveBackgroundMOC() {
    self.backgroundMOC.performBlock { () -> Void in
        do {
            try self.backgroundMOC.save()
            NSApp.replyToApplicationShouldTerminate(true)
        } catch {
            print("save error: bg")
        }
    }
}

func saveMainMOC() {
    self.managedObjectContext.performBlock { () -> Void in
        do {
            try self.managedObjectContext.save()
            self.saveBackgroundMOC()
        } catch {
            print("save error")
        }
    }
}

将applicationShouldTerminate() 更改为

func applicationShouldTerminate(sender: NSApplication) -> NSApplicationTerminateReply {
    if !managedObjectContext.commitEditing() {
        NSLog("\(NSStringFromClass(self.dynamicType)) unable to commit editing to terminate")
        return .TerminateCancel
    }

    if !managedObjectContext.hasChanges {
        return .TerminateNow
    }

    saveMainMOC()
    return .TerminateLater

}

这么慢的原因是我使用的是NSXMLStoreType 而不是NSSQLiteStoreType。

【问题讨论】:

  • 调用[[NSRunningApplication currentApplication] hide];,从applicationShouldTerminate:返回NSTerminateLater,然后让应用完成它应该做的事情。然后拨打replyToApplicationShouldTerminate:YES
  • 那么我应该在applicationShouldTerminate:发起的NSOperation中保存CoreData MOC并调用replyToApplicationShouldTerminate:YES然后返回NSTerminateLater吗?
  • 不,顺序是:1)。用户命令应用程序退出。 2)。应用程序委托接收applicationShouldTerminate: 调用。 3)。在这种方法中,您必须 a)。在后台线程和 b) 中启动保存过程。返回NSTerminateLater。 4)。保存过程完成后,您应该在主线程上调用replyToApplicationShouldTerminate:YES。
  • 还是没有运气。我在问题中添加了代码。
  • 只需将op.start() 包装在dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ }); 中,将self.managedObjectContext.save() 包装在self.managedObjectContext.performBlock 中。

标签: objective-c swift macos cocoa core-data


【解决方案1】:

退出应用程序可能需要一段时间,因为它首先会清空队列中的进程。 您想立即放弃丢弃父 MOC 或子 MOC 中的所有内容吗?但这会导致数据丢失。

如果你有多窗口应用程序,那么只关闭窗口而不退出应用程序。

如果您管理得当,数千个条目的处理和保存时间不应超过 5 秒。您的代码中可能存在一些漏洞,请尝试使用 Instruments 进行优化,CoreData 分析器工具将帮助您了解它消耗的时间量。

要隐藏窗口,您可以使用以下命令,所有核心数据处理都将在后台进行,一旦一切完成,应用程序将终止。

[self.window orderOut:nil];

【讨论】:

  • 不应丢弃数据更改。即使我在moc.save() 之前添加了windows.close(),窗口仍然存在,直到保存完成。
  • 如果你能展示代码你是如何编写和保存的,那就太好了。也可以试试CoreData Profiler。
  • 我使用 Xcode 在 AppDelegate 中生成的 applicationShouldTerminate: 进行保存。我认为所花费的时间是合理的。我想立即隐藏窗口,这样感觉很快。
  • 如果要隐藏窗口,请使用 [self.window orderOut:nil]; 。还更新了答案。
  • 谢谢。但我试过了。在保存完成之前,窗口不会隐藏,即使我在moc.save() 之前插入orderOut() 调用之前。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-02
  • 2012-01-22
  • 1970-01-01
  • 1970-01-01
  • 2015-01-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多