简体   繁体   中英

CoreData app takes too long to quit

My app may create / delete thousands of managed objects while running. I have used secondary NSManagedObjectContext s(MOCs) with NSPrivateQueueConcurrencyType and NSOperation s to make the app more responsive and most parts work well. But when I pressed ⌘Q and if the number of unsaved objects are large, the app hangs for quite a while before the window closes (the beach ball keeps on rotating...).

How to make the window disappear immediately, before the save of the MOC? I tried to insert window.close() in applicationShouldTerminate in the AppDelegate , but it has no effect.

My code for deletion is nothing special, except the hierachy is really large. Something like

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

Item is a hierarchic entity. Group has a one-to-many relationship to items. The removeItems is generated by CoreData with @NSManaged .

Many thanks.


Updates

I tried the following code, the save still blocks the 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
}

This doesn't make the window close first, when the amount of created / deleted managed objects is large.

Then I changed to the following, as suggested by @bteapot. Still has no effect. The window still won't close immediately.

@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
}

Finally I sort of solved the problem, though the UI is still blocked sometimes , even with the same test data.

The approach used can be found here: https://blog.codecentric.de/en/2014/11/concurrency-coredata/ , Core Data background context best practice , https://www.cocoanetics.com/2012/07/multi-context-coredata/

First I made a backgroundMOC with .PrivateQueueConcurrencyType

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

Then made it prent of the original moc.

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

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

    return managedObjectContext
}()

Two methods for the save.

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")
        }
    }
}

Change the applicationShouldTerminate() to

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

}

The reason it was so slow was I was using NSXMLStoreType instead of NSSQLiteStoreType .

Quitting an application might take a while since it will first empty the processes in queue. Do you want immediate quit discarding everything in the Parent or children MOCs? But this will result in data loss.

If you have multi window application then, then close the window only but not quit the app.

Also thousands of entry should not take longer than 5 seconds to get processed and saved, if you have managed it properly. There could be some loopholes in your code, try to optimize using Instruments, CoreData profiler tool that would help you to understand the amount of time it is eating up.

To hide the window you can use the below, and in background all the coredata processing will happen, and once everything is done the app will terminate.

[self.window orderOut:nil];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM