在 cellForRowAtIndexPath 中获取 iOS CoreData 后台线程
Posted
技术标签:
【中文标题】在 cellForRowAtIndexPath 中获取 iOS CoreData 后台线程【英文标题】:iOS CoreData Background Thread Fetching in cellForRowAtIndexPath 【发布时间】:2013-05-03 16:31:58 【问题描述】:我的应用中有一个表格视图,其中包含一个 NSFetchedResultsController 以加载到一些 CoreData 对象中。
随着表格在cellForRowAtIndexPath:
中构建,对于每个单元格,我必须进行一次提取以从另一个对象获取其他信息。
表中填满了 UserTasks,我必须从 UserSite 获取一些信息(UserTask 包含一个 siteID 属性)
我在后台线程中获取 UserSite 信息,并使用临时上下文。它工作正常,但它仍然希望在滚动时稍微滞后 UI。
Site *site = [_scannedSites objectForKey:task.siteID];
if(!site)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^
AppDelegate *ad = [AppDelegate sharedAppDelegate];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
temporaryContext.persistentStoreCoordinator = ad.persistentStoreCoordinator;
Site *site2 = [task getSiteWithContext:temporaryContext];
if(site2)
[ad.managedObjectContext performBlock:^
Site *mainContextObject = (Site *)[ad.managedObjectContext objectWithID:site2.objectID];
[_scannedSites mainContextObject forKey:task.siteID];
];
dispatch_async(dispatch_get_main_queue(), ^
Site *newSite = [_scannedSites objectForKey:task.siteID];
cell.lblCustName.text = newSite.siteName;
cell.lblAddr.text = [NSString stringWithFormat:@"%@ %@, %@", newSite.siteAddressLine1, newSite.siteCity, newSite.siteState];
cell.lblPhone.text = [self formatPhoneNum:newSite.phone];
);
else
dispatch_async(dispatch_get_main_queue(), ^
cell.lblCustName.text = @"";
cell.lblAddr.text = @"";
cell.lblPhone.text = @"";
);
);
else
cell.lblCustName.text = site.siteName;
cell.lblAddr.text = [NSString stringWithFormat:@"%@ %@, %@", site.siteAddressLine1, site.siteCity, site.siteState];
cell.lblPhone.text = [self formatPhoneNum:site.phone];
如您所见,如果您在 _scannedSites
中还没有任务的 UserSite 信息,则会启动一个后台线程,获取该任务的 UserSite,存储它,然后在主线程上填充详情。
就像我说的那样,滚动时会有一个非常烦人的延迟...我希望通过在后台进行工作来避免这种情况。
我是不是走错路了?
谢谢,感谢任何建议。
编辑
我在 CoreData 中创建了一个关系,现在我在 cellForRowAtIndexPath
中使用它。如果它还不存在,我创建它。这样效果更好。
Site *site = task.site;
if(!site)
AppDelegate *ad = [AppDelegate sharedAppDelegate];
NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.persistentStoreCoordinator = ad.persistentStoreCoordinator;
[temporaryContext performBlock:^
Site *tempContextSite = [task getSiteWithContext:temporaryContext];
[ad.managedObjectContext performBlock:^
Site *mainManagedObject = (Site *)[ad.managedObjectContext objectWithID:tempContextSite.objectID];
task.site = mainManagedObject;
NSError *error;
if (![temporaryContext save:&error])
[ad.managedObjectContext performBlock:^
NSError *e = nil;
if (![ad.managedObjectContext save:&e])
dispatch_async(dispatch_get_main_queue(), ^
cell.lblCustName.text = mainManagedObject.siteName;
cell.lblAddr.text = [NSString stringWithFormat:@"%@ %@, %@", mainManagedObject.siteAddressLine1, mainManagedObject.siteCity, mainManagedObject.siteState];
cell.lblPhone.text = [self formatPhoneNum:mainManagedObject.phone];
);
];
];
];
else
cell.lblCustName.text = site.siteName;
cell.lblAddr.text = [NSString stringWithFormat:@"%@ %@, %@", site.siteAddressLine1, site.siteCity, site.siteState];
cell.lblPhone.text = [self formatPhoneNum:site.phone];
【问题讨论】:
【参考方案1】:如果UserTask
与UserSite
相关,通常的Core Data 方法是在两者之间创建关系,然后在运行时使用该关系。因此,UserTask
将有一个名为 site
的属性,您只需向特定实例询问该属性的值。 ID 属性可能仍然存在,但只会在与某些外部数据存储(如服务器 API)同步时使用。
像这样存储 ID 和查找对象从根本上来说是一种笨拙的方法,它的设计目的是在运行时做很多不必要的工作。它避免了 Core Data 试图提供的所有便利,而是以艰难的方式做事。 在表格滚动时进行这项工作也是最糟糕的时间,因为此时性能问题最为明显。
如果您出于某种原因必须这样做,您可以通过提前查找所有 UserSite
实例而不是在表格滚动时进行优化。如果您知道所有 UserTask
实例,请在视图加载时通过一次调用获取所有站点。
【讨论】:
我已经研究过建立关系......但我正在处理一个庞大的数据集。当应用程序首次运行时,它会下载并插入大约 30 万条所有不同类型的记录(UserTask、UserAppointment、UserSite 等)。如果我要使用关系,我需要在插入点创建这种关系,不是吗?这反过来又会增加初始下载/插入过程的时间……我在这里可能完全错了,但这是我对关系的假设。 是的,它就是这样工作的。您可以在导入时完成工作,也可以稍后再做。就我个人而言,我宁愿导入时间稍慢一点,而不是糟糕的表格滚动性能。 所以我最终创建了关系.. 我仍在 cellForRowAtIndexPath 中进行工作,但我没有保留一个临时的 ID 字典,而是检查约会.userSite 是否存在.. 如果它不是我创造的吗,工作得更好!【参考方案2】:在cellForRowAtIndexPath:
中发送异步任务是个坏主意。如果用户滚动,将会创建一大堆线程,这些线程可能甚至没有必要。
最好有一个后台进程来获取您想要的信息,然后在需要时通知 UI 进行自我更新。这是非常标准的东西,你会很容易找到很多可靠实现的例子。
【讨论】:
我也构建了这个解决方案。我在使用这种方法时遇到的问题是,如果你最终滚动到表格下方并领先于后台获取......而不是你必须坐下在那里等待 UserSite 数据..这就是为什么我只为可见的单元格获取数据的路径。以上是关于在 cellForRowAtIndexPath 中获取 iOS CoreData 后台线程的主要内容,如果未能解决你的问题,请参考以下文章
在 cellForRowAtIndexPath 中获取 iOS CoreData 后台线程
UIView 在 cellForRowAtIndexPath 中重叠/再次创建
无法在 cellForRowAtIndexPath 中获取单元格框架或约束
为啥图像未显示在目标 C 的 cellforRowAtIndexPath 中