2009-07-08 84 views
16

我有一個UIScrollView,它內部並排加載一組圖像。你可以在這裏看到我的應用程序的例子:http://www.42restaurants.com。我的問題出現在內存使用情況中。我想懶加載圖像,因爲它們即將出現在屏幕上,並卸載不在屏幕上的圖像。正如你可以在代碼中看到的,我至少需要加載哪個映像,然後將加載部分分配給NSOperation並將其放置在NSOperationQueue上。除了簡單的滾動體驗外,一切都很好。在UIScrollView中加載優化圖像

我不知道如果任何人有任何想法,我怎樣才能使這個更加優化,使每個圖像的加載時間最小化或使滾動不暢少。

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

回答

15

我有點疑惑的「幹」的滾動。由於NSOperationQueue在單獨的線程上運行操作,因此我預計最糟糕的情況是您可能會看到空白的UIImageView出現在屏幕上。

首先,我會尋找的東西,單獨顯著影響的處理器的NSOperation不應與主線程干擾。其次我會查找圍繞NSOperation設置和執行的細節,這些細節可能會導致鎖定和同步問題,這些問題可能會中斷主線程,從而影響滾動。

幾個項目需要考慮:

  1. 嘗試加載你的ThumbnailView與在開始一個單一的形象和禁用的NSOperation排隊(只跳過繼「如果裝」檢查這會給你的一切。即時想法的NSOperation代碼是否影響性能。

  2. 記住-scrollViewDidScroll:可以單滾動操作過程中發生很多倍。根據如何滾動動作,以及如何你-centerThumbIndex是實施您可能會嘗試多次排隊相同的操作。如果你已經佔了這個在你-initWithOperableImage或-loaded那麼它有可能你在這裏的代碼導致同步/鎖定的問題(見下文3)。您應該跟蹤是否在ThumbnailView實例上使用「原子」屬性啓動了NSOperation。如果設置了該屬性,則防止對另一個操作進行排隊,並且只在NSOperation進程結束時取消設置該屬性(以及加載的)。

  3. 由於NSOperationQueue在其自己的線程中運行,因此請確保在NSOperation內執行的任何代碼都不會同步或鎖定到主線程。這將消除使用NSOperationQueue的所有優點。

  4. 確保您的「卸載」的操作具有優先級低於你的「負荷」運轉,因爲優先級是用戶體驗第一,內存保護第二。

  5. 請確保您至少保留了一頁或兩頁足夠的縮略圖,以便在NSOperationQueue落後的情況下,在空白縮略圖變爲可見之前,您的錯誤率很高。

  6. 確保您的負荷運行時只加載「縮放前」縮略圖,而不是加載完整大小的圖像,並重新縮放或處理。這在滾動操作中會帶來很多額外的開銷。更進一步,並確保您已將它們轉換爲PNG16而無需Alpha通道。這將至少使(4:1)尺寸減小,並且希望視覺圖像中沒有可檢測到的變化。另外考慮使用PVRTC格式的圖像,這樣會進一步減小尺寸(減少8:1)。這將大大減少從「磁盤」讀取圖像所需的時間。

如果對此沒有任何意義,我表示歉意。我沒有看到您發佈的代碼存在任何問題,並且問題更可能發生在您的NSOperation或ThumbnailView類實現中。沒有審查該守則,我可能不會有效地描述條件。

我會建議張貼裝卸您的NSOperation代碼,並在ThumbnailView的至少不夠了解它如何與實例的NSOperation交互。

希望這有助於在一定程度上,

巴尼

3

一種選擇,雖然少了賞心悅目的,是隻裝載圖像的滾動停止時。

設置一個標誌以禁止圖像加載:

-scrollViewWillBeginDragging: 

重新啓用加載圖像當滾動停止使用:

-scrollViewDidEndDragging:willDecelerate: 

UIScrollViewDelegate方法。當參數willDecelerate:NO時,移動已停止。

2

的問題是在這裏:

UIImage *image = [UIImage imageWithContentsOfFile:path]; 

看來,螺紋與否,當你從磁盤加載一個文件(這可能發生這種情況在主線上無論如何,我不完全確定)一切都會停滯。在其他情況下,您通常不會看到這一點,因爲如果有的話,您的移動面積不會太大。

1

在研究這個問題,我發現了兩個更多的資源可能會感興趣:

退房iPhone的樣本項目「的PageControl」:http://developer.apple.com/iphone/library/samplecode/PageControl/index.html

它延遲加載在一個UIScrollView視圖控制器。

  • 和 -

退房可可觸摸的lib:http://github.com/facebook/three20具有 'TTPhotoViewController' 類延遲加載照片/從網絡/磁盤縮略圖。

+0

謝謝你。我一定會看看。 – 2010-04-09 08:18:42

0

設置shouldRasterize = YES爲子視圖adde滾動視圖。它可以消除自定義創建的滾動視圖的干擾行爲,如魅力。 :)

也做一些使用在Xcode儀器分析。閱讀爲Ray Wenderlich創建的配置文件,它對我有很大的幫助。