CoreData 阻塞 UI

Posted

技术标签:

【中文标题】CoreData 阻塞 UI【英文标题】:CoreData blocking UI 【发布时间】:2016-09-15 10:43:37 【问题描述】:

我正在开发一个使用 CoreData 的现有项目:

在从 Web 服务接收到来自不同 CoreData 实体类型的许多项目后,它会阻塞 UI 线程数秒,即使我在另一个线程中使用它也是如此。

请帮帮我,有什么办法可以防止 CoreData 在项目快完成时以最少的更改阻塞 UI?

我是CoreData 的新手,很遗憾,我没有足够的时间研究文档或重新编写源代码。

我的数据控制器:

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)
        
    




我的课程是这样的:

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


还有我的网络服务类:

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):
        ...                
        


    )

【问题讨论】:

【参考方案1】:

其中一个问题可能与 QOS 级别有关。

QOS_CLASS_USER_INITIATED:用户发起的类代表任务 从 UI 启动并且可以异步执行。它 应在用户等待即时结果时使用,并且 继续用户交互所需的任务。

尝试使用QOS_CLASS_BACKGROUND

【讨论】:

我尝试使用它,但它导致核心数据操作崩溃。请问有什么解决方案或示例代码吗? 谢谢,我会等待你的帮助。 :) 当应用程序第一次启动时,我会一起调用一个请求列表,所有这些都是我在这里写的。 我使用 alamofire 从其他线程调用 Web 服务请求 然后在响应时我解析并在 UI 线程上使用它们,但我不知道如何在另一个线程上正确使用 CoreData【参考方案2】:

我建议两件事。首先,将私有队列上下文保存在 performBlock 函数中,就像保存主队列上下文一样。其次,在不同的地方设置断点,例如在保存之前和之后,以检查哪个线程正在调用该代码(使用 NSThread 的线程检查方法)。这可能会启发您了解从哪个线程执行的代码。希望这有助于引导您朝着正确的方向前进。

【讨论】:

以上是关于CoreData 阻塞 UI的主要内容,如果未能解决你的问题,请参考以下文章

CoreData从后台线程读取数据仍然阻塞UI界面的原因及解决

CoreData 版本控制和阻塞轻量级迁移

写入 coredata,即使在后台线程上完成,也会严重阻塞 UI

核心数据:大后台抓取能否阻塞主线程抓取请求?

从 NSData 转换到 UIImage 很慢,阻塞 UITableView

CoreData 在一个单独的线程中