延迟后在后台线程中处理核心数据

Posted

技术标签:

【中文标题】延迟后在后台线程中处理核心数据【英文标题】:Process Core Data in background thread after a delay 【发布时间】:2015-07-28 07:29:25 【问题描述】:

我有一个表格视图来显示从 API 获取的列表。

首先,我将 API 数据存储到 Core Data,然后在后续应用启动时,我将尝试更新列表。

我需要在后台线程中处理更新操作。应该在启动特定 ViewController 5 秒后调用更新操作

func updateGlossary() 
    var GlobalMainQueue: dispatch_queue_t 
        return dispatch_get_main_queue()
    
    let delayInSeconds = 5.0
    let popTime = dispatch_time(DISPATCH_TIME_NOW,
        Int64(delayInSeconds * Double(NSEC_PER_SEC)))
    dispatch_after(popTime, GlobalMainQueue) 
        self.showAlertMessage(message: "Updating Glossary")
        DataStore.GetToken( (token, error) in
            //Got Token
           //Call the API for updated data and store it to core data
        )
    

这里面有两个问题;

    进行更新时 UI 被阻止 当我在更新操作开始之前从控制器返回时,它仍然会从其他控制器更新(我认为问题是因为排队)

我正在使用 SwiftAlamofireCore Data

【问题讨论】:

【参考方案1】:

第一个问题:-

更新进行时用户界面被阻止

你应该以异步方式调用updateGlossary操作,它不会阻塞主线程:-

dispatch_async( dispatch_get_global_queue(0, 0), ^
    [self performSelector:@"updateGlossary" withObject:nil afterDelay:5.0]
);

第二个问题:-

当我在更新操作开始之前从控制器返回时,它仍然会从其他控制器更新(我认为问题是因为排队)

只需在视图控制器的 viewDidDisapper() 方法中停止线程

-(void)viewDidDisappear:(BOOL)animated

    [super viewDidDisappear:FALSE];
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(updateGlossary) object:nil];

【讨论】:

【参考方案2】:

不,不要使用dispatch_async 进行 CoreData 操作。 NSManagedObjectContext 本身不是线程安全的,会导致意外行为。你可能仍然使用dispatch_after 来调用主线程上的块。

您的代码很好,只是您的 getToken(:_) 可能发生在主线程上和/或您的数据库操作发生在主线程上,因此阻塞了 UI。

你可以像这样修改你的代码:

func updateGlossary() 
    var GlobalMainQueue: dispatch_queue_t 
        return dispatch_get_main_queue()
    
    let delayInSeconds = 5.0
    let popTime = dispatch_time(DISPATCH_TIME_NOW,
        Int64(delayInSeconds * Double(NSEC_PER_SEC)))
    dispatch_after(popTime, GlobalMainQueue) 
        self.showAlertMessage(message: "Updating Glossary")
        DataStore.GetToken( (token, error) in
            //Got Token
           //This will execute in the background
           self.callAPIWithToken(token, success:  (data) -> () in
              //Callback on the main thread
              self.privateManagedObjectContext.performBlock 
                //Will execute in its private thread
                //Insert to the database, save context
              
           )  (error) -> () in

           
        )
    

privateManagedObjectContextNSManagedObjectContext 的一个实例,其并发类型为.PrivateQueueConcurrencyType,其父存储为.MainQueueConcurrencyType。您使用privateManagedObjectContext 在后台插入并保存您的数据。这种方法是线程安全的,不会阻塞您的 UI。

Here 是一篇关于多上下文核心数据系统的好文章。

编辑 如果您想在离开时取消,我不建议使用dispatch_after。请改用NSTimer。让它在 5 秒后在主线程上进行服务调用;当您离开控制器时使其无效。

【讨论】:

var appDel: AppDelegate = (UIApplication.sharedApplication().delegate as!AppDelegate) var context: NSManagedObjectContext = appDel.managedObjectContext!这是我的上下文,当我使用它时,出现错误 NSInvalidArgumentException',原因:'Can only use -performBlock: on an NSManagedObjectContext that was created with a queue.' 正确。这是因为您使用的上下文是.ConfinementQueueConcurrencyType 类型。请通读我与文章链接的文章。这个方法对你来说会更清楚。 效果很好,但是如何在离开控制器时取消排队的任务 是取消请求还是取消数据库插入?【参考方案3】:

UI 被阻塞,因为您在 mainQueue 上进行 API 调用。创建一个不同的队列并进行 API 调用,稍后在您到达主队列后将 API 数据插入 Coredata。

我可以从您的代码中看到您正在使用GlobalMainQueue,它是dispatch_get_main_queue 的别名,因此您的 UI 卡住了。

解决方法如下:

 var GlobalBackgroundQueue: dispatch_queue_t

  return dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0)


dispatch_after(popTime, GlobalBackgroundQueue) 
 
    //Make the API Call
    dispatch_async(dispatch_get_main_queue()) 
    
        //Insert Into CoreData
    

【讨论】:

以上是关于延迟后在后台线程中处理核心数据的主要内容,如果未能解决你的问题,请参考以下文章

在后台线程中运行处理程序消息

删除核心数据对象并保存在后台线程中

在后台保存会导致响应时间延迟(iOS)

核心数据-后台线程中的更新实体会自动更改主线程中的 NSManagedObject 而无需合并-为啥?

从后台线程保存数据时核心数据崩溃

iOS/Objective-C:在后台线程中保存到核心数据