我将一系列个人资料图片存储在核心数据中Binary Data
(启用了允许外部存储选项,因此请勿跳转到我在Core Data中存储图像: )
每张图片都以UITableViewCell
显示。目前,当用户点击显示表视图时会有轻微的延迟,大概是因为它正在从Core Data加载它们,这对于在主线程上加载UI时具有足够的性能影响来锁定UI。
我想将图片加载到一个单独的后台线程中,以便立即显示表格视图,并且图像显示何时从Core Data加载。
我在-cellForRowAtIndexPath:
方法中使用以下代码尝试了解决方案in this post:
// Create the cell
MyCell *cell = [tableView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
// Get a reference to the object for this cell (an array obtained from Core Data previously in the code)
MyObject *theObject = [self.objectArray objectAtIndex:indexPath.item];
// Load the photo from Core Data relationship (ProfilePhoto entity)
UIImage *profilePhoto = [UIImage imageWithData:theObject.profilePhoto.photo];
// Set the name
cell.nameLabel.text = theObject.name;
dispatch_sync(dispatch_get_main_queue(), ^(void) {
// Set the photo
cell.photoImageView.image = profilePhoto;
});
});
// Return the cell
return cell;
但是,应用程序崩溃并出现以下错误(对于表视图中的每一行重复):
CoreData: error: exception during fetchRowForObjectID: statement is still active with userInfo of (null)
理解和解决这个问题的任何帮助都将不胜感激。
答案 0 :(得分:3)
solution which Marcus Zarra provided给了我一个关于如何解决这个问题的想法,但我认为为其他任何寻找解决方案的人提供实施的代码是值得的。所以,这是我的-cellForRowAtIndexPath:
方法的新代码:
// Create the cell
MyCell *cell = [tableView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
// Get a reference to the object for this cell
MyObject *theObject = [self.objectArray objectAtIndex:indexPath.item];
// Reset the image
cell.imageView.image = nil;
// Set the name
cell.nameLabel.text = theObject.name;
// Check if the object has a photo
if (theObject.profilePhoto != nil) {
// Create a new managed object context for the background thread
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.parentContext = self.managedObjectContext;
// Perform the operations on the new context
[backgroundContext performBlockAndWait:^{
// Fetch the object
NSError *error;
ProfilePhoto *profilePhotoObject = (ProfilePhoto *)[backgroundContext existingObjectWithID:theObject.profilePhoto.objectID error:&error];
// Create the image
UIImage *thePhoto = [UIImage imageWithData:myObject.profilePhoto];
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Set the photo
cell.imageView.image = thePhoto;
});
}];
}
return cell;
您还需要更改App Delegate文件中的行,该行创建托管对象上下文:
_managedObjectContext = [[NSManagedObjectContext alloc] init];
到
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
否则.parentContext
分配将无效。
希望这可以帮助其他人遇到这个问题!
答案 1 :(得分:2)
您违反了Core Data的线程规则。 NSManagedObjectContext
及其所有关联对象只能在创建它的线程上访问(或者线程已分配给它)。
您似乎正在访问该块中NSManagedObject
的实例,该实例位于另一个线程上,因此违反了线程包含规则。
您想通过将这些内容移动到后台队列来实现什么目标?
首先,您确定UIImage
创作是缓慢的吗?你有没有想过这段代码?这些图片有多大?它们是否太大而且成本是在调整大小?实际成本在哪里?
其次,UIImage
的创建可以在另一个线程上完成,但是您无法从多个线程访问上下文。这是一条难以克服任何稳定性的强硬路线。
第三,我不建议在Core Data中存储图像。不建议在SQLite文件中存储二进制数据。我将假设您正在使用外部记录存储选项。
解决这个问题的方法,假设UIImage
的加载和创建是实际问题,那就是:
NSManagedObject
(您可以传递objectID
,因为它是线程安全的。NSManagedObject
。如果这听起来像很多工作你会是对的!因此建议首先分析这个代码,并确保实际上是什么慢,并尝试找出它为什么慢。只需加载和初始化UIImage
不应该 慢。
答案 2 :(得分:1)
您在多个队列中使用相同的托管对象上下文,因此您的应用程序崩溃。
当您致电dispatch_get_global_queue
时,您会获得并发队列。当您在该队列上排队一堆块时,它们可以并行运行。在块中,您使用的核心数据对象都来自同一个托管对象上下文 - 这不是线程安全的。这几乎是崩溃的秘诀。
您应该做的是使用NSManagedObjectContext
队列限制。使用NSPrivateQueueConcurrencyType
或NSMainQueueConcurrencyType
创建上下文(私有在这里更好,因为您正在尝试从主队列中获取工作)。然后使用performBlockAndWait
来处理Core Data对象。这就像是
[self.context performBlockAndWait:^{
MyObject *theObject = [self.objectArray objectAtIndex:indexPath.item];
... etcetera ...
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Set the photo
cell.photoImageView.image = profilePhoto;
});
}];
块将按顺序执行,因此没有线程问题。它们将在上下文的私有队列中运行,该队列不会成为主队列。请注意,我更改了UI更新块以使用上面的dispatch_async
- 没有理由通过执行同步调用来阻止托管对象上下文队列。
答案 3 :(得分:0)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
dispatch_get_global_queue
为您提供一个后台队列,您可以在其上调度异步运行的后台任务(主要思想不会阻止您的用户界面)。
但是不允许在后台执行用户界面进程,因此dispatch_async
dispatch_get_main_queue
允许后台队列将用户界面更新分发回主队列。