UIScrollView中的优化图像加载

时间:2009-07-08 13:54:15

标签: iphone objective-c uiscrollview uiimage

我有一个UIScrollView,其中包含一组并排加载的图像。您可以在此处查看我的应用示例:http://www.42restaurants.com。我的问题在于内存使用情况。我想懒得加载图像,因为它们即将出现在屏幕上并卸载不在屏幕上的图像。正如您在代码中看到的那样,我至少需要加载哪个图像,然后将加载部分分配给NSOperation并将其放在NSOperationQueue上。除了不稳定的滚动体验之外,一切都很棒。

我不知道是否有人对如何使其更加优化有任何想法,以便最小化每个图像的加载时间或使滚动不那么生涩。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [self manageThumbs];    
}

- (void) manageThumbs{
    int centerIndex = [self centerThumbIndex];
    if(lastCenterIndex == centerIndex){
        return;
    }

    if(centerIndex >= totalThumbs){
        return;
    }

    NSRange unloadRange;
    NSRange loadRange;

    int totalChange = lastCenterIndex - centerIndex;
    if(totalChange > 0){ //scrolling backwards
        loadRange.length = fabsf(totalChange);
        loadRange.location = centerIndex - 5;
        unloadRange.length = fabsf(totalChange);
        unloadRange.location = centerIndex + 6;
    }else if(totalChange < 0){ //scrolling forwards
        unloadRange.length = fabsf(totalChange);
        unloadRange.location = centerIndex - 6;
        loadRange.length = fabsf(totalChange);
        loadRange.location = centerIndex + 5;
    }
    [self unloadImages:unloadRange];
    [self loadImages:loadRange];
    lastCenterIndex = centerIndex;

    return;
}

- (void) unloadImages:(NSRange)range{
    UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
    for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
        UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
        if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
            ThumbnailView *thumbView = (ThumbnailView *)subview;
            if(thumbView.loaded){
                UnloadImageOperation *unloadOperation = [[UnloadImageOperation alloc] initWithOperableImage:thumbView];
                [queue addOperation:unloadOperation];
                [unloadOperation release];
            }
        }
    }
}

- (void) loadImages:(NSRange)range{
    UIScrollView *scrollView = (UIScrollView *)[[self.view subviews] objectAtIndex:0];
    for(int i = 0; i < range.length && range.location + i < [scrollView.subviews count]; i++){
        UIView *subview = [scrollView.subviews objectAtIndex:(range.location + i)];
        if(subview != nil && [subview isKindOfClass:[ThumbnailView class]]){
            ThumbnailView *thumbView = (ThumbnailView *)subview;
            if(!thumbView.loaded){
                LoadImageOperation *loadOperation = [[LoadImageOperation alloc] initWithOperableImage:thumbView];
                [queue addOperation:loadOperation];
                [loadOperation release];
            }
        }
    }
}

修改 感谢真正的好回应。这是我的NSOperation代码和ThumbnailView代码。我在周末尝试了几件事,但我只是通过在滚动期间暂停操作队列并在滚动完成时恢复操作来提高性能。

以下是我的代码段:

//In the init method
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:4];


//In the thumbnail view the loadImage and unloadImage methods
- (void) loadImage{
    if(!loaded){
        NSString *filename = [NSString stringWithFormat:@"%03d-cover-front", recipe.identifier, recipe.identifier]; 
        NSString *directory = [NSString stringWithFormat:@"RestaurantContent/%03d", recipe.identifier];     

        NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:@"png" inDirectory:directory];
        UIImage *image = [UIImage imageWithContentsOfFile:path];

        imageView = [[ImageView alloc] initWithImage:image andFrame:CGRectMake(0.0f, 0.0f, 176.0f, 262.0f)];
        [self addSubview:imageView];
        [self sendSubviewToBack:imageView];
        [imageView release];
        loaded = YES;       
    }
}

- (void) unloadImage{
    if(loaded){
        [imageView removeFromSuperview];
        imageView = nil;
        loaded = NO;
    }
}

然后我的加载和卸载操作:

- (id) initWithOperableImage:(id<OperableImage>) anOperableImage{

    self = [super init];
    if (self != nil) {
        self.image = anOperableImage;
    }
    return self;
}

//This is the main method in the load image operation
- (void)main {
    [image loadImage];
}


//This is the main method in the unload image operation
- (void)main {
    [image unloadImage];
}

5 个答案:

答案 0 :(得分:15)

我对“生涩”的滚动感到有些困惑。由于NSOperationQueue在不同的线程上运行操作,我预计在最坏的情况下你可能会看到空的UIImageViews出现在屏幕上。

首先,我一直在寻找影响处理器的事情,因为NSOperation本身不应该干扰主线程。其次,我将查找有关NSOperation设置和执行的详细信息,这可能导致锁定和同步问题,这些问题可能会中断主线程,从而影响滚动。

需要考虑的几个方面:

  1. 尝试在开始时使用单个图像加载ThumbnailView并禁用NSOperation排队(只需跳过“if loaded”检查后的所有内容。这将让您立即了解NSOperation代码是否会影响性能。< / p>

  2. 请注意,-scrollViewDidScroll:在单个滚动操作过程中可能会出现多次次。根据滚动的移动方式以及-centerThumbIndex的实现方式,您可能会尝试多次对相同的操作进行排队。如果您在-initWithOperableImage中已经考虑了这个问题或者已经加载了 - 那么您的代码可能导致同步/锁定问题(参见下面的3)。您应该跟踪是否已使用ThumbnailView实例上的“atomic”属性启动了NSOperation。如果设置了该属性,则阻止对另一个操作进行排队,并且仅在NSOperation进程结束时取消设置该属性(以及加载)。

  3. 由于NSOperationQueue在其自己的线程中运行,因此请确保在NSOperation中执行的任何代码都不会同步或锁定到主线程。这将消除使用NSOperationQueue的所有优点。

  4. 确保“卸载”操作的优先级低于“加载”操作,因为优先级是用户体验优先,内存保护第二。

  5. 确保为前后至少一页或两页保留足够的缩略图,这样如果NSOperationQueue落后,则在空白缩略图变为可见之前,您会有很高的误差。

  6. 确保您的加载操作仅加载“预缩放”缩略图,而不是加载完整大小的图像并重新缩放或处理。在滚动动作的中间,这将是很多额外的开销。更进一步,确保你没有alpha通道将它们转换为PNG16。这将使尺寸减小至少(4:1),并且希望视觉图像中没有可检测到的变化。还要考虑使用PVRTC格式图像,这样可以进一步缩小尺寸(减少8:1)。这将大大减少从“磁盘”读取图像所需的时间。

  7. 如果其中任何一项没有意义,我道歉。我没有看到您发布的代码存在任何问题,并且更有可能在您的NSOperation或ThumbnailView类实现中出现问题。如果不审查该代码,我可能无法有效地描述这些条件。

    我建议您发布用于加载和卸载的NSOperation代码以及至少足够的ThumbnailView来了解它与NSOperation实例的交互方式。

    希望这在某种程度上有所帮助,

    巴尼

答案 1 :(得分:3)

一个选项虽然视觉上不那么令人满意,但只是在滚动停止时加载图像。

设置一个标志以禁用图像加载:

-scrollViewWillBeginDragging:

使用以下内容重新启用滚动停止时加载图像:

-scrollViewDidEndDragging:willDecelerate:

UIScrollViewDelegate方法。当willDecelerate:参数为NO时,移动已停止。

答案 2 :(得分:2)

问题在于:

 UIImage *image = [UIImage imageWithContentsOfFile:path];

当您从磁盘加载文件时,似乎是否有线程(这可能发生在主线程上,无论如何,我都不完全确定)一切都停滞不前。在其他情况下,您通常不会看到这种情况,因为如果有的话,您没有这么大的区域。

答案 3 :(得分:1)

在研究这个问题时,我发现了另外两个可能感兴趣的资源:

查看iPhone示例项目“PageControl”:http://developer.apple.com/iphone/library/samplecode/PageControl/index.html

它延迟加载UIScrollView中的视图控制器。

  • 和 -

查看cocoa touch lib:http://github.com/facebook/three20,其中有一个'TTPhotoViewController'类,它从网页/磁盘加载延迟加载照片/缩略图。

答案 4 :(得分:0)

为滚动视图的子内容视图adde设置shouldRasterize = YES。它被视为删除自定义创建的滚动视图的生涩行为,如魅力。 :)

也可以使用Xcode中的乐器进行一些分析。浏览Ray Wenderlich为分析创建的教程,这对我帮助很大。