16

我有一个 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];
}
4

5 回答 5

15

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

首先,我会寻找对处理器有显着影响的东西,因为单独的 NSOperation 不应该干扰主线程。其次,我会寻找有关 NSOperation 设置和执行的细节,这些细节可能会导致锁定和同步问题,这些问题可能会中断主线程并因此影响滚动。

需要考虑的几个项目:

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

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

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

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

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

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

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

我建议发布您的 NSOperation 代码以进行加载和卸载,并至少发布足够的 ThumbnailView 以了解它如何与 NSOperation 实例交互。

希望这在一定程度上有所帮助,

巴尼

于 2009-07-09T11:05:09.067 回答
3

一种选择虽然在视觉上不太令人愉悦,但仅在滚动停止时才加载图像。

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

-scrollViewWillBeginDragging:

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

-scrollViewDidEndDragging:willDecelerate:

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

于 2009-07-08T14:49:32.437 回答
2

问题在这里:

 UIImage *image = [UIImage imageWithContentsOfFile:path];

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

于 2010-04-08T18:01:58.473 回答
1

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

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

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

  • 和 -

查看可可触摸库: http : //github.com/facebook/three20,它有一个“TTPhotoViewController”类,可以从网络/磁盘延迟加载照片/缩略图。

于 2010-04-08T21:40:12.717 回答
0

为添加到滚动视图的子内容视图设置 shouldRasterize = YES。可以看到它消除了自定义创建的滚动视图的生涩行为,就像魅力一样。:)

还可以使用 Xcode 中的工具进行一些分析。浏览Ray Wenderlich为分析创建的教程,它对我有很大帮助。

于 2013-09-15T05:32:31.870 回答