4

我有一个与我试图用我的应用程序解决的问题相关的一般 Objective-c 模式/实践问题。我在这里找不到类似的以客观 c 为重点的问题/答案。

我的应用程序包含一个可变的对象数组,我称之为“记录”。该应用程序收集记录并通过以下两种方式之一将它们放入该数组中:

  1. 它从应用程序沙箱中本地可用的 SQLite 数据库读取数据。读取通常非常快。
  2. 它从 Web 服务异步请求数据,等待它完成然后解析数据。读取速度可能很快,但通常不是。

有时,应用程序从数据库中读取数据 (1) 并从 Web 服务 (2) 请求数据基本上同时进行。通常情况下,(1) 将在 (2) 完成之前完成,并且将 Records 添加到可变数组不会导致冲突。

我担心在某些时候我的 SQLite 读取过程会花费比预期更长的时间,它会尝试在异步请求完成的同时将对象添加到可变数组并执行相同的操作;或相反亦然。这些是似乎难以测试的边缘情况,但这肯定会使我的应用程序崩溃或至少导致我的记录数组出现问题。

我还应该指出,记录将被合并到可变数组中。例如:如果 (1) 首先运行并返回 10 条记录,然后在 (2) 完成并返回 5 条记录后不久,我的可变数组将包含所有 15 条记录。我正在组合数据而不是覆盖它。

我想知道的是:

  • 当(1)或(2)进程完成时,我将对象添加到同一个可变数组实例中是否安全?
  • 在objective-c中是否有一个好的模式/实践来实现这种处理?
  • 这是否涉及锁定对可变数组的访问,因此当 (1) 向其中添加对象时 (2) 在 (1) 完成之前不能添加任何对象?

我很感激你可以分享的任何信息。

[编辑#1]

对于后代,我发现这个 URL 对理解如何使用 NSOperations 和 NSOperationQueue 有很大帮助。它有点过时了,但仍然有效:

http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues

此外,它没有具体谈论我要解决的问题,但它使用的示例实用且易于理解。

[编辑#2]

我决定采用 danh 建议的方法,我将在本地阅读,并在本地阅读完成后根据需要访问我的 Web 服务(无论如何应该很快)。Taht 说,我将尝试完全避免同步问题。为什么?因为苹果是这么说的,所以在这里:

http://developer.apple.com/library/IOS/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8-SW8

完全避免同步

对于您从事的任何新项目,甚至对于现有项目,设计您的代码和数据结构以避免需要同步是最好的解决方案。尽管锁和其他同步工具很有用,但它们确实会影响任何应用程序的性能。如果整体设计导致特定资源之间的高度争用,您的线程可能会等待更长时间。

实现并发的最佳方式是减少并发任务之间的交互和相互依赖。如果每个任务都对自己的私有数据集进行操作,则不需要使用锁来保护该数据。即使在两个任务共享一个公共数据集的情况下,您也可以查看对该数据集进行分区或为每个任务提供其自己的副本的方法。当然,复制数据集也有其成本,因此在做出决定之前,您必须权衡这些成本与同步成本。

4

4 回答 4

2

当(1)或(2)进程完成时,我将对象添加到同一个可变数组实例中是否安全?

绝对不。NSArray 以及其余的集合类是不同步的。当您添加和删除对象时,您可以将它们与某种锁结合使用,但这肯定比仅创建两个数组(每个操作一个)并在它们都完成时合并它们要慢得多。

在objective-c中是否有一个好的模式/实践来实现这种处理?

很不幸的是,不行。您能想到的最多的是触发布尔值,或在常见回调中将整数增加到某个数字。要明白我的意思,这里有一些伪代码:

- (void)someAsyncOpDidFinish:(NSSomeOperation*)op {
    finshedOperations++;
    if (finshedOperations == 2) {
       finshedOperations = 0;
       //Both are finished, process
    }
}

这是否涉及锁定对可变数组的访问,因此当 (1) 向其中添加对象时 (2) 在 (1) 完成之前不能添加任何对象?

是的,见上文。

于 2013-03-27T22:48:17.467 回答
2

您应该锁定您的数组修改,或者在主线程中安排您的修改。SQL 提取可能在主线程中运行,因此在您的远程提取代码中,您可以执行以下操作:

dispatch_async(dispatch_get_main_queue(), ^{
    [myArray addObject: newThing];
}];

如果您要添加一堆对象,这会很慢,因为它会在每个记录的调度程序上放置一个新任务。您可以将记录捆绑在线程中的单独数组中,并使用 addObjectsFromArray 添加临时数组:如果是这种情况。

于 2013-03-27T22:49:12.300 回答
1

就个人而言,我倾向于并发NSOperationQueue并添加两个检索操作操作,一个用于数据库操作,一个用于网络操作。然后,我将有一个专用的串行队列,用于将记录添加到 中NSMutableArray,两个并发检索操作中的每一个都将使用该队列将记录添加到可变数组中。这样,您就有一个用于添加记录的队列,但从另一个并发队列上运行的两个检索操作提供数据。如果您需要知道两个并发检索操作何时完成,我会在该并发队列中添加第三个操作,将其依赖项设置为两个检索操作,这将在两个检索操作完成时自动触发。

于 2013-03-27T22:57:17.310 回答
1

除了上面的好建议,考虑不要同时启动 GET 和 sql。

[self doTheLocalLookupThen:^{
    // update the array and ui
    [self doTheServerGetThen:^{
        // update the array and ui
    }];
}];


- (void)doTheLocalLookupThen:(void (^)(void))completion {

    if ([self skipTheLocalLookup]) return completion();
    // do the local lookup, invoke completion
}

- (void)doTheServerGetThen:(void (^)(void))completion {

    if ([self skipTheServerGet]) return completion();
    // do the server get, invoke completion
}
于 2013-03-27T23:03:07.797 回答