10

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

我已经多次修改了我的实现,我可以看到性能改进,但它仍然无法像照片应用程序那样流畅地滚动。

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

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

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

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

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

使用 NSCache 缓存从 CoreData 加载的图像后,滚动速度明显更快,但由于在 NSCache 中尚不可用时,仍需要从 CoreData 加载图像,因此滚动仍会不时出现抖动。

因此,必须有更好的方法,我可以将所有图像预加载到内存中,但由于可能有大量或多行图像,所以我根本不打算预加载图像。

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

4

3 回答 3

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;
    }

如果您使用某种单例图像存储管理器,您会期望管理器处理缓存/数据库访问的细节,这简化了这个示例。

这部分

            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
                    }
                }

将被替换为

      UIImage* thumbnail = [[ImageManager singleton] getImage];

(您不会使用完成块,因为当您返回主队列时,您实际上是在 GCD 中提供了一个完成块)

于 2013-01-31T11:40:51.600 回答
0

我使用了一个名为JMImageCache的类将图像缓存到磁盘,然后只在 CoreData 中存储本地 URL(和远程 URL,如果你愿意的话)。您还可以生成一个缩略图,并将其保存到磁盘并将缩略图 URL 与图像 URL 一起存储在核心数据中,并在表视图中使用缩略图。

于 2013-01-31T11:34:55.520 回答
0

在核心数据中为图像添加一个 NSValueTransformer 的子类,

代码如下:

+ (BOOL)allowsReverseTransformation {
    return YES;
}
+ (Class)transformedValueClass {
    return [NSData class];
}
 - (id)transformedValue:(id)value {
    return UIImageJPEGRepresentation(value, 0.5);
 }
- (id)reverseTransformedValue:(id)value {
    return [[UIImage alloc] initWithData:value];
} 
于 2015-11-02T07:05:58.033 回答