在cellForRowAtIndexPath处从CoreData加载图像会减慢滚动速度

mkt*_*kto 10 objective-c uitableview

我正在开发一个UITableView,它非常像iOS的原生Photo应用程序:它有很多行,每行有4个图像缩略图.(即每个UITableViewCell都有4个UIImageViews)所有缩略图都是从Core Data加载的.

我已多次修改我的实现,我可以看到性能改进,但仍然无法像Photo应用程序那样顺利滚动.

我需要建议如何正确缓存照片以获得最佳性能.这是我试过的:

1.我的第一次尝试(滚动时非常滞后)

  • 图像以CoreData中的Transformable类型存储.
  • 在cellForRow函数中,每个图像都是从CoreData中即时获取的.

2.第二次尝试(更快,但滚动时仍然有点滞后)

  • 图像与二进制数据类型一起存储,CoreData中勾选了"外部存储"选项.
  • 在cellForRow函数中,每个图像首先从Core Data加载,然后存储到内存中的NSCache中,因此下次cellForRow触发时,如果可用,我们将直接使用NSCache中的UIImage.

使用NSCache缓存从CoreData加载的图像后,滚动显然更快,但由于在NSCache中尚不可用时仍需要从CoreData加载图像,因此滚动仍会不时地生涩.

所以,必须有一个更好的方法,我可以将所有图像预加载到内存中,但由于可能有大量或多行图像,所以我根本没有计划预加载图像.

我还能做些什么来在cellForRowAtIndexPath中更快地加载图像?

fou*_*dry 26

无论数据来自何处,都要保持滚动顺畅,您需要在单独的线程上获取数据,并且只有在内存中存储数据时才更新UI.Grand Central Despatch是您的最佳选择.这是一个骨架,假设你有一个self.photos字典,其中包含对图像文件的文本引用.图像缩略图可以加载或不加载到实时字典中; 可能在文件系统缓存中,也可能不在; 否则从网上商店取货.它可以使用Core Data,但平滑滚动的关键是你不要在任何地方等待数据.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    static NSString *CellIdentifier = @"Photo Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    //identify the data you are after
    id photo = [self.photos objectAtIndex:indexPath.row];
        // Configure the cell based on photo id

      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //move to an asynchronous thread to fetch your image data
            UIImage* thumbnail = //get thumbnail from photo id dictionary (fastest)
            if (!thumbnail) {    //if it's not in the dictionary
                thumbnail =      //get it from the cache  (slower)
                                 // update the dictionary
                if (!thumbnail) {   //if it's not in the cache
                  thumbnail =       //fetch it from the (online?) database (slowest)
                                    // update cache and dictionary
                    }
                }
            }
            if (thumbnail) {  
                dispatch_async(dispatch_get_main_queue(), ^{
                //return to the main thread to update the UI
                    if ([[tableView indexPathsForVisibleRows] containsObject:indexPath]) {
                    //check that the relevant data is still required
                        UITableViewCell * correctCell = [self.tableView cellForRowAtIndexPath:indexPath];
                        //get the correct cell (it might have changed)
                        [[correctCell imageView] setImage:thumbnail];
                        [correctCell setNeedsLayout];
                    }
                });
            }
        });
    return cell;
    }
Run Code Online (Sandbox Code Playgroud)

如果您使用某种单例映像存储管理器,您可能希望管理器处理缓存/数据库访问的详细信息,这简化了此示例.

这部分

            UIImage* thumbnail = //get thumbnail from photo id dictionary (fastest)
            if (!thumbnail) {    //if it's not in the dictionary
                thumbnail =      //get it from the cache  (slower)
                                 // update the dictionary
                if (!thumbnail) {   //if it's not in the cache
                  thumbnail =       //fetch it from the (online?) database (slowest)
                                    // update cache and dictionary
                    }
                }
Run Code Online (Sandbox Code Playgroud)

将被替换为类似的东西

      UIImage* thumbnail = [[ImageManager singleton] getImage];
Run Code Online (Sandbox Code Playgroud)

(当你返回主队列时,你不会使用完成块,因为你在GCD中有效地提供了一个)