使用UITableView和GCD减少延迟

时间:2011-12-01 22:49:01

标签: objective-c ios uitableview grand-central-dispatch lag

我有一个UITableView,大约有10个子类UITableViewCell名为TBPostSnapCell。初始化时,每个单元格设置两个变量,UIImage通过GCD下载,或从存储在用户文档目录中的缓存中检索。

出于某种原因,这会导致tableView明显滞后,从而破坏应用程序的用户体验。表

请告诉我如何减少这种滞后?

tableView ... cellForRowAtIndexPath:

if (post.postType == TBPostTypeSnap || post.snaps != nil) {

        TBPostSnapCell *snapCell = (TBPostSnapCell *) [tableView dequeueReusableCellWithIdentifier:snapID];

        if (snapCell == nil) {

            snapCell = [[[NSBundle mainBundle] loadNibNamed:@"TBPostSnapCell" owner:self options:nil] objectAtIndex:0];

            [snapCell setPost:[posts objectAtIndex:indexPath.row]];

            [snapCell.bottomImageView setImage:[UIImage imageNamed:[NSString stringWithFormat:@"%d", (indexPath.row % 6) +1]]];
        }

    [snapCell.commentsButton setTag:indexPath.row];
    [snapCell.commentsButton addTarget:self action:@selector(comments:) forControlEvents:UIControlEventTouchDown];
    [snapCell setSelectionStyle:UITableViewCellSelectionStyleNone];

    return snapCell;
}

TBSnapCell.m

- (void) setPost:(TBPost *) _post {

    if (post != _post) {
        [post release];
        post = [_post retain];
    }
    ...

    if (self.snap == nil) {

        NSString *str = [[_post snaps] objectForKey:TBImageOriginalURL];
        NSURL *url = [NSURL URLWithString:str];
        [TBImageDownloader downloadImageAtURL:url completion:^(UIImage *image) {
            [self setSnap:image];
        }];
    }

    if (self.authorAvatar == nil) {
        ...
        NSURL *url = [[[_post user] avatars] objectForKey:[[TBForrstr sharedForrstr] stringForPhotoSize:TBPhotoSizeSmall]];

        [TBImageDownloader downloadImageAtURL:url completion:^(UIImage *image) {
            [self setAuthorAvatar:image];
        }];
        ...
    }

}

TBImageDownloader.m

+ (void) downloadImageAtURL:(NSURL *)url completion:(TBImageDownloadCompletion)_block {

    if ([self hasWrittenDataToFilePath:filePathForURL(url)]) {
        [self imageForURL:filePathForURL(url) callback:^(UIImage * image) {
            _block(image); //gets UIImage from NSDocumentsDirectory via GCD
        }];
        return;
    }

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    dispatch_async(queue, ^{
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self writeImageData:UIImagePNGRepresentation(image) toFilePath:filePathForURL(url)];
            _block(image);
        });
    });
}

1 个答案:

答案 0 :(得分:2)

首先要尝试将DISPATCH_QUEUE_PRIORITY_HIGH(也称为最重要的工作,永远忘记一切)转换为DISPATCH_QUEUE_PRIORITY_LOW。

如果这不能解决问题,你可以尝试通过dispatch_sources进行http流量,但这需要做很多工作。

你也可能只是试图限制使用信号量的飞行中http提取的数量,真正的诀窍是决定最佳限制是什么,因为“好”数字将取决于网络,你的CPU和内存压力。也许基准2,4和8有几个配置,看看是否有足够的模式来概括。

好的,我们只试一次,将queue = ...替换为:

static dispatch_once_t once;
static dispatch_queue_t queue = NULL;
dispatch_once(&once, ^{
    queue = dispatch_queue_create("com.blah.url-fetch", NULL);
});

保留其余代码。这可能是最少的溅射,但可能无法非常快地加载图像。

对于更一般的情况,请删除我刚给你的更改,我们将继续处理:

dispatch_async(queue, ^{
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self writeImageData:UIImagePNGRepresentation(image) toFilePath:filePathForURL(url)];
        _block(image);
    });
});

替换为:

static dispatch_once_t once;
static const int max_in_flight = 2;  // Also try 4, 8, and maybe some other numbers
static dispatch_semaphore_t limit = NULL;
dispatch_once(&once, ^{
    limit = dispatch_semaphore_create(max_in_flight);
});
dispatch_async(queue, ^{
    dispatch_semaphore_wait(limit, DISPATCH_TIME_FOREVER);
    UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
    //  (or you might want the dispatch_semaphore_signal here, and not below)
    dispatch_async(dispatch_get_main_queue(), ^{
        [self writeImageData:UIImagePNGRepresentation(image) toFilePath:filePathForURL(url)];
        _block(image);
        dispatch_semaphore_signal(limit);
    });
});

注意:我还没有测试过这些代码,甚至看它是否编译。如上所述,它只允许2个线程执行两个嵌套块中的大部分代码。您可能希望将dispatch_semaphore_signal移动到注释行。这将限制您进行两次提取/图像创建,但是允许它们将图像数据写入文件并调用_block回调重叠。

顺便说一句,你做了很多文件I / O,它在闪存上比任何磁盘都要快,但是如果你仍然在寻找可能是另一个攻击场所的性能获胜。例如,可能会将UIImage保留在内存中,直到您收到内存不足警告,然后再将其写入磁盘。