【问题标题】:iOS CoreData Background Thread Fetching in cellForRowAtIndexPath在 cellForRowAtIndexPath 中获取 iOS CoreData 后台线程
【发布时间】: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];
    }

【问题讨论】:

    标签: ios multithreading core-data uitableview gdc


    【解决方案1】:

    如果UserTaskUserSite 相关,通常的Core Data 方法是在两者之间创建关系,然后在运行时使用该关系。因此,UserTask 将有一个名为 site 的属性,您只需向特定实例询问该属性的值。 ID 属性可能仍然存在,但只会在与某些外部数据存储(如服务器 API)同步时使用。

    像这样存储 ID 和查找对象从根本上来说是一种笨拙的方法,它的设计目的是在运行时做很多不必要的工作。它避免了 Core Data 试图提供的所有便利,而是以艰难的方式做事。 在表格滚动时进行这项工作也是最糟糕的时间,因为此时性能问题最为明显。

    如果您出于某种原因必须这样做,您可以通过提前查找所有 UserSite 实例而不是在表格滚动时进行优化。如果您知道所有 UserTask 实例,请在视图加载时通过一次调用获取所有站点。

    【讨论】:

    • 我已经研究过建立关系......但我正在处理一个庞大的数据集。当应用程序首次运行时,它会下载并插入大约 30 万条所有不同类型的记录(UserTask、UserAppointment、UserSite 等)。如果我要使用关系,我需要在插入点创建这种关系,不是吗?这反过来又会增加初始下载/插入过程的时间……我在这里可能完全错了,但这是我对关系的假设。
    • 是的,它就是这样工作的。您可以在导入时完成工作,也可以稍后再做。就我个人而言,我宁愿导入时间稍慢一点,而不是糟糕的表格滚动性能。
    • 所以我最终创建了关系.. 我仍在 cellForRowAtIndexPath 中进行工作,但我没有保留一个临时的 ID 字典,而是检查约会.userSite 是否存在.. 如果它不是我创造的吗,工作得更好!
    【解决方案2】:

    cellForRowAtIndexPath: 中发送异步任务是个坏主意。如果用户滚动,将会创建一大堆线程,这些线程可能甚至没有必要。

    最好有一个后台进程来获取您想要的信息,然后在需要时通知 UI 进行自我更新。这是非常标准的东西,你会很容易找到很多可靠实现的例子。

    【讨论】:

    • 我也构建了这个解决方案。我在使用这种方法时遇到的问题是,如果你最终滚动到表格下方并领先于后台获取......而不是你必须坐下在那里等待 UserSite 数据..这就是为什么我只为可见的单元格获取数据的路径。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-12
    • 1970-01-01
    相关资源
    最近更新 更多