从NSData加载UIImage导致生涩的UITableView滚动

时间:2013-01-27 22:01:49

标签: ios objective-c xcode uitableview nsdata

在从NSData加载图像时,我正在让我的tableview顺畅滚动。

一些背景......我将图像NSData存储在核心数据中(使用'允许外部存储'选项,所以我不认为存储URL或类似的东西就是这里的解决方案)。此外,我存储两种类型的数据,一种是高分辨率的UIImagePNGRepresentation,另一种是低分辨率的UIImageJPGRepresentation。我正在为我的桌面视图加载低分辨率图像。条目对象上的图像属性不存储在核心数据中,事后其设置。我是这样做的:

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

我认为我的下一个攻击计划是乱用存储的图像大小(如评论中所述),或者在表格滚动时尝试不更新imageViews。对这两种方法的任何想法都非常感谢!我做完那件事后再检查

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

滚动现在非常流畅。真棒

2 个答案:

答案 0 :(得分:2)

有几个问题:

  1. 我想知道这两条线花了多少时间:

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

    虽然我认为entry.image是一张缓存的图片,但我并不相信objectAtIndexPath:indexPath实际上并没有从核心数据中检索NSData(这是一个很好的部分)将图像存储在本地数据库而不是文件系统中的性能问题。我倾向于在后台队列中这样做。

  2. 我不明白为什么要向oQueue添加两个操作(可以同时操作!)。它应该如下所示,因此在检索图像之前不要尝试调度到主队列:

    [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. 这可能不重要,但你真的不应该为每个单元格创建一个新的NSOperationQueue。您应该定义一个NSOperationQueue的类属性,在viewDidLoad中对其进行初始化,然后在cellForRowAtIndexPath中使用该队列。我确定这不重要,但是为表格的每一行创建一个队列的开销并不值得。 (如果你去过基于服务器的图像检索,使用单个队列非常重要,这样你就可以控制maxConcurrentOperationCount。)

答案 1 :(得分:0)

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

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