Sina KH
Sina KH

Reputation: 565

CoreData blocking UI

I'm working on an existing project, using CoreData, that:

Adds many many items from different CoreData Entity types, after receiving them from web service, but it blocks the UI Thread for many seconds, even if I use it in another thread.

Please help me, is there any way to prevent CoreData from blocking UI with minimum changes as the project is almost completed?

I'm new to CoreData and I don't have enough time to study the doc or reprogram the source code, unfortunately.

My DataController:

class DataController {

var managedObjectContext: NSManagedObjectContext
let modelName = "something"


init(closure:()->()) {

    guard let modelURL = NSBundle.mainBundle().URLForResource(modelName, withExtension: "momd"),
        let managedObjectModel = NSManagedObjectModel.init(contentsOfURL: modelURL)
        else {
            fatalError("DataController - COULD NOT INIT MANAGED OBJECT MODEL")
    }

    let coordinator = NSPersistentStoreCoordinator.init(managedObjectModel: managedObjectModel)

    managedObjectContext = {
        let parentContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
        parentContext.persistentStoreCoordinator = coordinator

        let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
        managedObjectContext.parentContext = parentContext
        return managedObjectContext
    }()

    dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {

        let options = [
            NSMigratePersistentStoresAutomaticallyOption: true,
            NSInferMappingModelAutomaticallyOption: true,
            NSSQLitePragmasOption: ["journal_mode": "DELETE"]
        ]

        let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last
        let storeURL = NSURL.init(string: "something", relativeToURL: documentsURL)


        do {
            try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options)

            dispatch_async(dispatch_get_main_queue()) {
                closure()
            }
        }
        catch let error as NSError {
            fatalError("DataController - COULD NOT INIT SQLITE STORE: \(error.localizedDescription)")
        }
    }
}

func save(moc: NSManagedObjectContext? = nil) {

    var managedObjectContext = moc

    if moc == nil {
        managedObjectContext = self.managedObjectContext
    }

    managedObjectContext!.performBlockAndWait {

        if managedObjectContext!.hasChanges {

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

        if let parentContext = managedObjectContext!.parentContext {
            self.save(parentContext)
        }
    }
}


}

My classes are something like:

class MOCity: NSManagedObject {

@NSManaged var id: NSNumber?
@NSManaged var name: String?

// Insert code here to add functionality to your managed object subclass
class func save(id: NSNumber, json: JSON, managedObjectContext: NSManagedObjectContext) {

    guard let newObject = MOCity.getById(id, create: true, managedObjectContext: managedObjectContext) else {
        fatalError("City - NO OBJECT")
    }

    newObject.id                <-- json["Id"]
    newObject.name              <-- json["Name"]

}

internal class func getById(id: NSNumber, create: Bool = false, managedObjectContext: NSManagedObjectContext) -> MOCity? {

    let fetchRequest = NSFetchRequest(entityName: "City")
    fetchRequest.predicate = NSPredicate(format: "id = \(id)")

    do {
        guard let fetchResults = try managedObjectContext.executeFetchRequest(fetchRequest) as? [MOCity] else {
            fatalError("city - NO FETCH RESULTS")
        }

        if fetchResults.count > 0 {
            return fetchResults.last
        }
        else if fetchResults.count == 0 {

            if create {
                guard let object = NSEntityDescription.insertNewObjectForEntityForName("City", inManagedObjectContext: managedObjectContext) as? MOCity else {
                    fatalError("getCity - COULD NOT CREATE OBJECT")
                }
                return object
            } else {
                return nil
            }
        }
    }
    catch let error as NSError {
    ...
    }

    return nil
}
}

And my web service class:

Alamofire.request(.POST, url,parameters:data).validate().responseJSON(completionHandler: {response in

        switch response.result {

        case .Success:

            if let jsonData = response.result.value {

                if let array = jsonData as? NSArray {

                for arrayItem in array {
                    MOCity.save(arrayItem["Id"] as! NSNumber, json: JSON(arrayItem as! NSDictionary)!, managedObjectContext: self.dataController.managedObjectContext)
                }

                self.dataController.save()

                }

            }
            break
        case .Failure(let error):
        ...                
        }


    })

Upvotes: 2

Views: 1302

Answers (2)

BJ Miller
BJ Miller

Reputation: 1548

I would suggest two things. First, save the private queue context inside a performBlock function just as you do the main queue context. Second, set breakpoints at different places, such as before and after saves, to check which thread is calling that code (using NSThread's thread checking methods). That may enlighten you as to what code is being executed from which thread. Hope that helps steer you in the right direction.

Upvotes: 1

Oleg Gordiichuk
Oleg Gordiichuk

Reputation: 15512

One of the issue could be connected for the QOS level.

QOS_CLASS_USER_INITIATED: The user initiated class represents tasks that are initiated from the UI and can be performed asynchronously. It should be used when the user is waiting for immediate results, and for tasks required to continue user interaction.

Try to use QOS_CLASS_BACKGROUND.

Upvotes: 2

Related Questions