在 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】:

如果UserTaskUserSite 相关,通常的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 中

nib 项目中的 CellForRowAtIndexPath

我应该在 tableView(_:cellForRowAtIndexPath:) 方法中为自定义单元格写啥?