我已经了解到,一般来说,密集型任务应该在后台线程上进行,就像它们在主线程上一样,它们会阻止用户交互和界面更新。
核心数据是否属于该保护伞?我收到great answer关于在UITableView
中异步加载图片的问题,但我很好奇如何使用Core Data作为后端。
我知道Apple有一个Core Data Concurrency指南,但我很好奇在哪种情况下应该在后台使用Core Data。
作为一个简单的例子,假设我正在制作Twitter客户端,并希望获得所有推文信息(推文文本,用户名,用户头像,链接图像等)。我异步下载该信息并从Twitter API接收一些我随后解析的JSON。当我将信息添加到Core Data时,我应该dispatch_async(dispatch_get_main_queue()...)
吗?
我还想下载用户的头像,但我想单独发布推文,这样我就可以尽快展示推文,然后在准备好时展示图片。所以我异步下载图像。我应该异步更新该核心数据项吗?
我是否处于根本不需要多线程核心数据的情况?如果是的话,什么时候我需要呢?
答案 0 :(得分:1)
核心数据确实属于这一范畴。特别是用于下载和保存数据,但也可能用于取决于数据存储中的对象数量和要应用的谓词。
通常,我会将所有来自服务器的对象创建和保存推送到后台线程。我只是在主线程上更新并保存对象,如果它们是用户生成的(因为它只是一个,缓慢且不经常更新)。
下载您的Twitter数据就是一个很好的例子,因为可能需要处理大量数据。您应该在后台线程上处理并保存数据,并将其保存到那里的持久存储中。然后,持久性存储应该将更改合并到主线程上下文中(假设您已经很好地配置了上下文 - 使用像MagicalRecord那样的第三方框架)。
同样,对于头像更新,您已经在后台线程中,所以您可能会留在那里: - )
您现在可能需要使用多个线程。如果您只下载了5条最新的推文,那么您可能不会注意到这些差异。但是使用框架可以使多线程相对变得容易。并且使用获取的结果控制器可以非常容易地知道何时应该在主线程上更新UI。因此,花时间了解设置并使用它是值得的。
答案 1 :(得分:1)
我已经了解到,一般来说,密集型任务应该在后台线程上进行,就像他们在主线程上一样,他们会阻止用户交互和界面更新。
核心数据是否属于该保护伞?
是的,实际上。
核心数据任务可以和应该 - 尽可能 - 在后台线程或非主队列上执行。
值得注意的是,每个托管对象都与某个执行上下文(一个线程或一个调度队列)相关联。访问托管对象必须仅在此执行上下文中执行。此关联来自于每个托管对象都使用某个托管对象上下文注册,并且此托管对象上下文在创建时与特定执行上下文相关联。
另见:
因此,当显示托管对象的属性时,这涉及UIKit,并且由于UIKit方法必须在主线程上执行,因此托管对象的上下文必须与主线程或主队列相关联。
否则,Core Data和用户代码可以从任意执行上下文访问托管对象,只要这是与托管对象关联的对象。
下面的图片是一个工具时间配置文件,它清楚地显示了如何在不同的线程上分发核心数据任务:
突出显示的"秒杀"在CPU活动中显示执行以下任务的任务:
正如我们所看到的,只有26.4%的CPU负载将在主线程上执行。
答案 2 :(得分:0)
处理此类行为的最佳方法是使用多个NSManagedObjectContexts
。您创建的“主要”上下文如下:
_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_mainManagedObjectContext.persistentStoreCoordinator = [self mainPersistentStoreCoordinator];
您将要在不同的NSManagedObjectContext
上进行任何繁重的写操作,以避免在导入时阻塞您的UI线程(这对于Main上下文的大型或频繁操作非常明显)。
要实现这一目标,你可以这样做:
NSManagedObjectContext
并将“父”设置为主ManagedObjectContext(因此它将合并您保存时导入的数据)performBlock:
或performBlockAndWait:
API在其自己的专用队列(在后台)写入该上下文示例(使用AFNetworking):
[_apiClient GET:[NSString stringWithFormat:@"/mydata"]
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSError* jsonError;
id jsonResponse = [NSJSONSerialization JSONObjectWithData:operation.responseData options:kNilOptions error:&jsonError];
if (!jsonError) {
NSArray* parsedData = (NSArray*)jsonResponse;
completionBlock(parsedShares, nil);
} else {
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = YOUR_MAIN_CONTEXT; // when you save this context, it will merge its changes into your main context!
// the following code will occur in a background queue for you that CoreData manages
[context performBlock:^{
// THIS IS WHERE YOU IMPORT YOUR DATA INTO CORE DATA
// THIS IS WHERE YOU IMPORT YOUR DATA INTO CORE DATA
// THIS IS WHERE YOU IMPORT YOUR DATA INTO CORE DATA
if (![context save:&saveError]) {
NSLog(@"Error saving context: %@", saveError);
} else {
NSLog(@"Saved data import in background!");
}
}];
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"There was an error: %@", error)
}];
答案 3 :(得分:0)
正如我在你提到的另一个问题中提到的那样,这很大程度上取决于操作需要多长时间。如果所需的计算没有明显的延迟,那么无论如何都要懒惰并在主线程上执行核心数据。
在你的另一个例子中,你从twitter请求20个项目,解析20个项目并将它们粘贴到CoreData中并不会引人注目。这里最好的方法是可能继续一次只获取20个,并在每个块可用时在前台更新。
在一个请求中从twitter下载所有项目将花费大量时间和计算,并且可能值得创建单独的ManagedObjectModel并将其与前台模型同步。由于您确实拥有单向数据(它总是流向twitter->核心数据 - >用户界面),因此冲突的可能性很小,因此您可以轻松使用NSManagedObjectContextDidSaveNotification和mergeChangesFromContextDidSaveNotification: