2009-07-08 15 views
16

Mam UIScrollView, który ma zestaw obrazów załadowanych obok siebie w środku. Możesz zobaczyć przykład mojej aplikacji tutaj: http://www.42restaurants.com. Mój problem dotyczy użycia pamięci. Chcę leniwie wczytać obrazy, gdy są one wyświetlane na ekranie i usuwać obrazy, które nie są wyświetlane na ekranie. Jak widać w kodzie, wykreślam co najmniej obraz, który muszę załadować, a następnie przypisać część ładującą do NSOperation i umieścić ją w NSOperationQueue. Wszystko działa świetnie, oprócz szarpanego przewijania.Zoptymalizowane ładowanie obrazu w UIScrollView

Nie wiem, czy ktoś ma jakieś pomysły co do tego, w jaki sposób mogę uczynić to jeszcze bardziej zoptymalizowanym, tak aby czas ładowania każdego obrazu był zminimalizowany lub aby przewijanie było mniej gwałtowne.

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

EDIT: Dzięki za naprawdę wielkie odpowiedzi. Oto mój kod NSOperation i kod ThumbnailView. Próbowałem kilku rzeczy przez weekend, ale udało mi się poprawić wydajność, zawieszając kolejkę operacji podczas przewijania i wznawiając ją po zakończeniu przewijania.

Oto mój kod urywki:

//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; 
    } 
} 

Wtedy mój ładunek i rozładunek operacje:

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

Odpowiedz

15

Jestem trochę zaskoczony "przewlekłym" przewijaniem. Ponieważ NSOperationQueue uruchamia operacje w osobnych wątkach, których oczekiwałbym w najgorszym wypadku, na ekranie mogą pojawić się puste UIImageViews.

Przede wszystkim będę szukał rzeczy, które znacząco wpływają na procesor, ponieważ sama operacja NSSperation nie powinna zakłócać głównego wątku. Po drugie, będę szukał szczegółów dotyczących konfiguracji i wykonywania NSOperation, które mogą powodować blokowanie i synchronizowanie problemów, które mogą przerwać główny wątek, a co za tym idzie przewijanie.

kilka rzeczy do rozważenia:

  1. Spróbuj załadować swoje ThumbnailView pojedynczemu obrazu na początku i wyłączanie NSOperation kolejce (po prostu pominąć wszystko po „jeśli załadowany” check To daje. natychmiastowe pomysł czy kod NSOperation ma wpływu na wydajność.

  2. Należy pamiętać, że mogą wystąpić -scrollViewDidScroll: wiele razy w trakcie jednej akcji przewijania. w zależności od tego, jak do ruchów spiralnych iw jaki sposób jest -centerThumbIndex zaimplementowane, możesz próbować kolejkować te same akcje kilka razy.Jeśli uwzględniłeś to w swoim pliku -initWithOperableImage lub -loaded, to możliwe, że Twój kod tutaj powoduje problemy z synchronizacją/zablokowaniem (patrz 3 poniżej). Powinieneś śledzić, czy operacja NSOperation została zainicjowana przy użyciu właściwości "atomowej" w instancji ThumbnailView. Zapobiegaj kolejkowaniu kolejnej operacji, jeśli ta właściwość jest ustawiona i zeruj tę właściwość (wraz z obciążeniem) na końcu procesów NSOperation.

  3. Ponieważ NSOperationQueue działa we własnym wątku (-ach), upewnij się, że żaden z twoich kodów wykonywanych w ramach NSOperation nie jest synchronizowany lub blokowany do głównego wątku. To wyeliminowałoby wszystkie zalety korzystania z NSOperationQueue.

  4. Upewnij się, że operacja "rozładuj" ma niższy priorytet niż operacja "ładowanie", ponieważ priorytetem jest najpierw wrażenia użytkownika, a po drugie ochrona pamięci.

  5. Upewnij się, że zachowujesz wystarczającą liczbę miniaturek co najmniej na stronę lub dwie strony do przodu i do tyłu, tak więc jeśli NSOperationQueue jest w tyle, masz duży margines błędu, zanim puste miniatury staną się widoczne.

  6. Upewnij się, że operacja ładowania ładuje tylko miniaturę "wstępnie skalowaną" i nie ładuje obrazu w pełnym rozmiarze ani przeskalowania ani przetwarzania. To będzie dużo dodatkowych kosztów w trakcie przewijania. Idź jeszcze dalej i upewnij się, że przekonwertowałeś je na PNG16 bez kanału alfa. Da to co najmniej redukcję rozmiaru (4: 1), przy miejmy nadzieję, że nie da się wykrywalnych zmian w obrazie wizualnym. Rozważ także użycie obrazów w formacie PVRTC, które jeszcze bardziej zmniejszą rozmiar (redukcja o 8: 1). Znacznie skróci to czas potrzebny na odczytanie obrazów z "dysku".

Przepraszam, jeśli coś z tego nie ma sensu. Nie widzę żadnych problemów z opublikowanym przez Ciebie kodem, a problemy pojawiają się częściej w implementacjach klas NSOperation lub ThumbnailView. Bez przeglądu tego kodu, mogę nie opisywać skutecznie warunków.

Polecam umieszczenie kodu NSOperation do załadowania i wyładowania i co najmniej tyle ThumbnailView, aby zrozumieć, w jaki sposób współdziała z instancjami NSOperation.

Nadzieja ta przyczyniła się do pewnego stopnia,

Barney

3

Jedną z opcji, choć mniej wizualnie miłe, to tylko load gdy przewijanie zatrzyma.

Ustaw flagę, aby wyłączyć ładowanie obrazów w:

-scrollViewWillBeginDragging: 

ponowne włączenie ładowania obrazów gdy przewijanie zatrzymuje się przy użyciu:

-scrollViewDidEndDragging:willDecelerate: 

UIScrollViewDelegate metody. Gdy parametr willDecelerate: ma wartość NO, ruch został zatrzymany.

2

problem jest tutaj:

UIImage *image = [UIImage imageWithContentsOfFile:path]; 

Wydaje się, że gwintowane lub nie, kiedy załadować plik z dysku (co może, co się dzieje na głównym wątku, niezależnie, nie jestem całkowicie pewny) wszystko stoi. Zwykle nie widzisz tego w innych sytuacjach, ponieważ nie masz tak dużego obszaru w ruchu, jeśli w ogóle.

1

Badając ten problem, znalazłem dwa więcej zasobów, które mogą być interesujące:

Sprawdź przykładowy projekt iPhone „PageControl”: http://developer.apple.com/iphone/library/samplecode/PageControl/index.html

To leniwe ładunki zobaczyć kontrolerów w UIScrollView.

  • i -

Zapoznaj się z dotykowego kakao lib: http://github.com/facebook/three20 który posiada klasę 'A' TTPhotoViewController że zdjęcia leniwe Obciążenia/web/miniatury z dysku.

+0

Dzięki za to. Na pewno popatrzę. –

0

Ustaw shouldRasterize = YES dla widoku pod treści adde do scrollview. Wygląda na to, aby usunąć zachłanność niestandardowego utworzonego widoku przewijania, jak w przypadku czaru. :)

Wykonaj również profilowanie za pomocą instrumentów w Xcode. Przejdź przez samouczki stworzone do profilowania przez Ray Wenderlich bardzo mi to pomogło.