Jacob Kazal
Jacob Kazal

Reputation: 149

frozen UI when working with core data .mainQueueConcurrencyType

Problem: UI is frozen while working with core data with .mainQueueConcurrencyType thread

UI: Any UI updates, UITable scrolling etc, But in this question I use example of SwiftSpinner which is activity indicator, https://github.com/icanzilb/SwiftSpinner

setup :

iOS : 11.4 , device : iPhone 7 plus, Xcode: 9.4.1

my code looks like this

let bgContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)


func doCoreData(progress : @escaping (Double) -> ()) {
    bgContext.performAndWait {
        for i in 1...10{
            let cent : Double = Double(i)/Double(10)
            // do some coredata work adding and updating
            progress(cent)
            // hide SwiftSpinner at the end
            if i == 10 {
                SwiftSpinner.hide()
            }
        }
    }
}

func orgnizeThings (){
    doCoreData { (cent) in
        DispatchQueue.main.async {
            let perCent = cent * 100
            SwiftSpinner.show(progress: cent, title: "loading \(perCent)")
        }
    }
}

calling the orgnizeThings() function will get the Core Data work done but I would like to to show the user how far the function is done

in this setup the SwiftSpinner UIView is frozen

Upvotes: 1

Views: 1143

Answers (1)

Deep Arora
Deep Arora

Reputation: 2040

Core data is not thread safe. Changing the concurrency type from MainQueueConcurrencyType to PrivateQueueConcurrencyType can cause the app to crash if multiple threads are writing at the same time. Better approach would be to have multiple manage object contexts, with parent child relationship:

 let mainMOC = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
 let childMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
 childMoc.parentContext = mainMoc

  childMOC.performBlock{
            for i in 1...10{
                let cent : Double = Double(i)/Double(10)
                // do some coredata work adding and updating
                progress(cent)

            }

            do {
                try childMoc.save()
                mainMOC.performBlockAndWait{
                    do {
                        try mainMoc.save()
                        SwiftSpinner.hide()
                    } catch {
                        fatalError("Failure to save context: \(error)")
                    }
                }
            } catch{
                fatalError("Failure to save context: \(error)")
            }

  }

When the child context is saved, the changes go to the parent context. When the parent context is saved, changes go to the persistent store coordinator. The main thread won't be blocked, because all the heavy write operations will be done in the childMoc which is of PrivateConcurrencyType.

If the performance is still poor, it would be because writing from MainContext to disc is an expensive operation. Instead of parent context writing to the disc directly, you can create a master context which will write to the disc and Main context changes will go to Maser Context once the Main Context is saved.

enter image description here

For more details, read this medium post: https://medium.com/soundwave-stories/core-data-cffe22efe716

Upvotes: 4

Related Questions