从NSData加载UIImage导致UITableView滚动不流畅

3
我在从NSData中加载图像时,无法使我的tableview平滑滚动。一些背景信息...我正在使用Core Data存储图像NSData(使用“允许外部存储”选项,因此我认为在此处不是存储URL或类似东西的解决方案)。此外,我存储两种类型的数据,一种是高分辨率的UIImagePNGRepresentation,另一种是低分辨率的UIImageJPGRepresentation。我正在为我的table view加载低分辨率的图像。条目对象上的图像属性未存储在core data中,而是事后设置。以下是我的操作方式:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{        

    DMLogTableViewCell *cell = (DMLogTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:@"LogCell" forIndexPath:indexPath];

    [cell setGradients:@[[UIColor whiteColor], [UIColor lightTextColor]]];
    Entry *entry = [self.fetchedResultsController objectAtIndexPath:indexPath];
    UIImage *entryImage = entry.image;

    if (entryImage)
        cell.rightImageView.image = entryImage;
    else {
        NSOperationQueue *oQueue = [[NSOperationQueue alloc] init];
        [oQueue addOperationWithBlock:^(void) {
            UIImage *image = [UIImage imageWithData:entry.photo.lowResImageData];
            if (!image) image = [UIImage imageNamed:@"defaultImage.png"];
            entry.image = image;
        }];
        [oQueue addOperationWithBlock:^(void) {
            [[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
                DMLogTableViewCell *cell = (DMLogTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
                cell.rightImageView.image = entry.image;
            }];
        }];

    }

    return cell;
}

有没有关于如何使这个运行更顺畅的想法?非常感谢您的帮助!

谢谢

更新

这让我更接近目标了,但还是有一点卡顿...(在cellForRowAtIndexPath中)

    UIImage *entryImage = entry.image;

    if (entryImage)
        cell.rightImageView.image = entryImage;
    else {
        [self.imageQueue addOperationWithBlock:^(void) {
            UIImage *image = [UIImage imageWithData:entry.photo.lowResImageData];
            if (!image) image = [UIImage imageNamed:@"bills_moduleIcon.png"];
            entry.image = image;
            [entry.image preload];

            [[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
                DMLogTableViewCell *cell = (DMLogTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
                cell.rightImageView.image = entry.image;
            }];
        }];
    }

那个预加载方法在UIImageView的一个分类中,看起来像这样:

- (void)preload
{
    CGImageRef ref = self.CGImage;
    size_t width = CGImageGetWidth(ref);
    size_t height = CGImageGetHeight(ref);
    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width * 4, space, kCGImageAlphaPremultipliedFirst);
    CGColorSpaceRelease(space);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), ref);
    CGContextRelease(context);
}

我认为我的下一步计划是尝试调整存储的图像大小(如评论中提到的)或者尝试在表格滚动时不更新imageView。对于这两种方法,如果有任何想法,都非常感谢!我会在完成后回来查看。
更新2 非常感谢所有帮助我的人!以下是最终解决方案(在cellForRowAtIndexPath:中):
    if (entryImage)
        cell.rightImageView.image = entryImage;
    else {
        [self.imageQueue addOperationWithBlock:^(void) {
            UIImage *image = [UIImage imageWithData:entry.photo.lowResImageData];
            if (!image) image = [UIImage imageNamed:@"bills_moduleIcon.png"];
            entry.image = [image scaleToSize:cell.rightImageView.frame.size];

            [[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
                DMLogTableViewCell *cell = (DMLogTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
                cell.rightImageView.image = entry.image;                
            }];
        }];
    }

为UIImage添加新类别方法:

-(UIImage*)scaleToSize:(CGSize)size
{
    UIGraphicsBeginImageContext(size);
    [self drawInRect:CGRectMake(0, 0, size.width, size.height)];
    UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

滚动现在非常顺畅。太棒了。

感谢帮助。调整大小确实是影响滚动性能的最终问题。谢谢! - Sean Danzeiser
2个回答

2
一些问题:
  1. I'd want to see how much time these two lines are taking:

    Entry *entry = [self.fetchedResultsController objectAtIndexPath:indexPath];
    UIImage *entryImage = entry.image;
    

    While I gather that entry.image is a cached image, I wouldn't be so confident that objectAtIndexPath:indexPath isn't actually retrieving the NSData from Core Data (which is a good portion of the performance issue in storing images in a local database rather than the file system). I'd be inclined to do this in the background queue.

  2. I don't understand why you're adding two operations to oQueue (which could operate concurrently!). It should just be as follows, so you don't try to dispatch to main queue until the image is retrieved:

    [oQueue addOperationWithBlock:^(void) {
        UIImage *image = [UIImage imageWithData:entry.photo.lowResImageData];
        if (!image) image = [UIImage imageNamed:@"defaultImage.png"];
        entry.image = image;
    
        [[NSOperationQueue mainQueue] addOperationWithBlock:^(void) {
            DMLogTableViewCell *cell = (DMLogTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
            cell.rightImageView.image = entry.image;
        }];
    }];
    
  3. It's probably not material, but you really shouldn't create a new NSOperationQueue for each cell. You should define a class property that is a NSOperationQueue, initialize it in viewDidLoad, and then use that queue in cellForRowAtIndexPath. It's not material, I'm sure, but it's not worth the overhead of creating a queue for each row of your table. (And if you ever go to server-based image retrieval, using a single queue is very important so you can control the maxConcurrentOperationCount.)


2
+1,非常好的观点。另一个可能的问题是当他设置图像视图的图像时。如果图像在磁盘上存储的大小与显示的大小不同,它也会导致卡顿,因为每次都要将图像缩放到正确的大小。有很多代码(在SO和Google上)可以“调整”图像大小,但当你将其保存到磁盘时,它保存的是整个图像而不是缩略图。 - lnafziger
我在考虑这是下一个要调查的好事情...我离成功更近了,但滚动仍然不是完美流畅的...现在更新以展示我的进展。 - Sean Danzeiser

0

UIImage不会在需要绘制图像之前进行解码,因此当您将图像分配给图像视图时,它实际上是在主线程上发生的。

我有一个UIImage类别,它将强制解码图像(应在后台线程上运行),链接在这里:https://github.com/tonymillion/TMDiskCache


此外,我会执行这个过程(将低分辨率的jpg NSData存储在CoreData对象中,在主图像下载时显示)。 - Tony Million

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接