使用多线程在iOS上保存数据

时间:2014-01-11 14:30:08

标签: ios multithreading cocoa-touch

我正在开发一款保存一些数据的iPhone应用程序。我正在使用NSKeyedArchiver类的归档方法将数据保存到磁盘。我想定期将数据保存到磁盘。问题是,当数据变大时,需要更多时间,实际上会中断用户当前的操作。

因此,我想使用多线程来解决这个问题。根据我对多线程的理解,当我想将数据保存到磁盘时,我应该创建一个新线程,在新线程上运行保存任务,然后终止线程。我也应该创建线程,以便在应用程序终止时不会立即终止,以完成保存数据。这样,用户就可以继续与界面进行交互。

话虽这么说,我不熟悉这些工作的实际代码......上面的代码在代码中是什么样的?

3 个答案:

答案 0 :(得分:6)

一些想法。

  1. 您希望使用串行调度队列或操作队列。

    注意,我们可能希望它以串行方式写入持久存储(例如,如果您将其保存到相同的文件名),即不允许在先前的保存完成之前启动另一个保存。我怀疑你的偶然保存不可能在前一个保存仍在进行时触发保存,但作为一般原则,你不应该使用并发队列,除非你编写支持并发操作的代码(我们在这里不做)。这意味着您不使用GCD全局队列。

    例如,使用Grand Central Dispatch(GCD)创建串行调度队列将是:

    @property (nonatomic, strong) dispatch_queue_t queue;
    

    然后实例化(例如在viewDidLoad中):

    self.queue = dispatch_queue_create("com.domain.app.savequeue", 0);
    

    然后使用此队列

    dispatch_async(self.queue, ^{
        // do your saving here
    });
    

    有关并发技术的评论,请参阅Concurrency Programming Guide。调度队列(GCD)和操作队列都是可靠的选择。

  2. 您可能需要注意同步问题。如果您的应用在保存期间开始更改数据,该怎么办?这里有很多选项,但最简单的方法是在将保存任务分派到后台队列之前将数据复制到主队列中的某些临时对象:

    // copy the model data to some temporary object(s)
    
    dispatch_async(self.queue, ^{
        // save the temporary object(s) here
    });
    
  3. 或者,不是创建模型的副本,您可以选择(如果您不熟悉GCD,这有点复杂)使用Apple的“读写器”模式的变体在WWDC 2012视频Asynchronous Design Patterns with Blocks, GCD, and XPC中进行了讨论。最重要的是,您可以排队以执行异步写入持久存储,还可以使用“屏障”将更新与模型同步(请参阅GCD参考中的Using Barriers):

    self.queue = dispatch_queue_create("com.domain.app.modelupdates", DISPATCH_QUEUE_CONCURRENT);
    

    然后,当您想要保存到磁盘时,可以执行

    dispatch_async(self.queue, ^{
        // save model to persistent storage
    });
    

    但是,每当您想要更新模型时,都应该使用屏障,以便模型的更新不会与任何读取/保存任务同时发生:

    dispatch_barrier_async(self.queue, ^{
        // update model here
    });
    

    而且,无论何时从模型中读取,您都会:

    dispatch_sync(self.queue, ^{
        // read from model here
    });
    

    理论上,如果您担心可能会经常进行保存操作,以至于当您启动下一个保存操作时仍然可以进行一次保存,那么您实际上可能会使用两个队列,一个串行队列用于保存操作(上面的第1点),以及此处列出的并发队列,用于同步过程。

  4. 最后,Putz1103是正确的,如果在保存过程中可能终止应用程序,您可能希望添加代码以允许写入持久存储完成:

    dispatch_async(self.queue, ^{
        UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
            // handle timeout gracefully if you can
    
            [[UIApplication sharedApplication] endBackgroundTask:taskId];
            taskId = UIBackgroundTaskInvalid;
        }];
    
        // save model to persistent storage
    
        // when done, indicate that the task has ended
    
        if (taskId != UIBackgroundTaskInvalid) {
            [[UIApplication sharedApplication] endBackgroundTask:taskId];
            taskId = UIBackgroundTaskInvalid;
        }
    });
    

答案 1 :(得分:1)

将多线程添加到多个线程之间共享数据的应用程序(在这种情况下,用户创建的数据和您要保存的数据)是一项难以管理的任务。

不是一次创建线程或尝试保存所有数据,而是将要保存的数据保存到内部“必须保存关闭”列表中,然后定期将其从一个N元素中取出主线。

如果您到达用户离开屏幕或应用程序的位置,请立即将队列中剩余的所有工作保存到数据库中。

您可以创建一个简单的定时事件(每秒几次)来完成工作,这是一种非常简单的方法。

  • 您可以明确控制每次更新保存的项目数。
  • 你永远不应该遇到并发问题。
  • 您永远不必担心线程启动/停止/终止 问题或互斥。

创建它:

-(void)viewDidAppear:(BOOL)animated
{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(saveData) userInfo:nil repeats:YES];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveAllRemainingItems) name:@"APP EXITING KEY" object:nil]
}

您的更新功能:

-(void)saveData
{
    if([itemsToSave count] > 0)
    {
       ....save off N items, remove from the list
    }
}

-(void)saveAllRemainingItems
{
    while([itemsToSave count] > 0)
    {
        ...save first item.
       [itemsToSave removeObjectAtIndex:0];
    }
}

当你离开时:

-(void)viewWillDisappear:(BOOL)animated
{
   [self.timer invalidate];
   [[NSNotificationCenter defaultCenter] removeObserver:self];
   [self saveAllRemainingData];
}

要确保您处理“app is closing”情况,请在您的app delegate:

- (void)applicationWillTerminate:(UIApplication *)application 
{
   [[NSNotificationCenter defaultCenter] postNotificationName:@"APP EXITING KEY" object:nil];

    ...OTHER CLEANUP ACTIVITIES
}

答案 2 :(得分:0)

你可以用不同的方式在IOS中实现多线程,如NSThread,Operation Queues和GCD。 GCD是现在最好的approch,它使用块。您可以像这样在不同的线程中执行代码。您可以在任何方法中使用它。

void performArchiveData{

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           //Now you are in different thread. You can add your code in here.

       });
}