2009-07-08 15 views
16

Ho un UIScrollView che ha un insieme di immagini caricate affiancate al suo interno. Puoi vedere un esempio della mia app qui: http://www.42restaurants.com. Il mio problema arriva con l'utilizzo della memoria. Voglio pigro caricare le immagini mentre stanno per apparire sullo schermo e scaricare le immagini che non sono sullo schermo. Come puoi vedere nel codice, risolvo al minimo l'immagine che devo caricare, quindi assegna la porzione di caricamento a NSOperation e la posiziono su NSOperationQueue. Tutto funziona alla grande, a parte un'esperienza di scorrimento a scatti.Caricamento immagine ottimizzato in un UIScrollView

Non so se qualcuno ha qualche idea su come posso renderlo ancora più ottimizzato, in modo che il tempo di caricamento di ogni immagine sia ridotto al minimo o che lo scorrimento sia meno a scatti.

- (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: Grazie per le risposte davvero grande. Ecco il mio codice NSOperation e il codice ThumbnailView. Ho provato un paio di cose durante il fine settimana, ma sono riuscito a migliorare le prestazioni sospendendo la coda delle operazioni durante lo scorrimento e riprendendo quando lo scorrimento è terminato.

Ecco miei frammenti di codice:

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

Poi mio carico e scarico operazioni:

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

risposta

15

Sono un po 'sconcertato dallo scorrimento "a scatti". Poiché NSOperationQueue esegue operazioni su thread separati che mi sarei aspettato nel peggiore dei casi, potresti vedere UIImageViews vuoto visualizzato sullo schermo.

Innanzitutto, cercherò in primo luogo cose che hanno un impatto sul processore in modo significativo poiché NSOperation da solo non dovrebbe interferire con il thread principale. In secondo luogo, cercherò i dettagli relativi all'impostazione e all'esecuzione di NSOperation che potrebbero causare problemi di blocco e sincronizzazione che potrebbero interrompere il thread principale e quindi influire sullo scorrimento.

Alcuni elementi da considerare:

  1. Provare a caricare i tuoi ThumbnailView di con una sola immagine alla partenza e la disattivazione del NSOperation in coda (basta saltare tutto seguendo il "se caricato" check Questo vi darà una. idea immediata se il codice NSOperation sta urtando prestazioni.

  2. Tenete a mente che -scrollViewDidScroll: può accadere molte volte nel corso di una singola azione di scorrimento. a seconda di come per gli spostamenti di scorrimento e come il vostro -centerThumbIndex è implementato si potrebbe tentare di accodare le stesse azioni più volte.Se ne hai tenuto conto in -initWithOperableImage o -loaded allora è possibile che il tuo codice qui causi problemi di sincronizzazione/blocco (vedi 3 sotto). È necessario verificare se è stata avviata una NSOperation utilizzando una proprietà "atomica" nell'istanza ThumbnailView. Impedire l'accodamento di un'altra operazione se questa proprietà è impostata e solo annullare l'impostazione di tale proprietà (insieme a quella caricata) alla fine dei processi NSOperation.

  3. Poiché NSOperationQueue funziona in un proprio thread (s), assicurarsi che nessuno del codice in esecuzione in NSOperation sia sincronizzato o bloccato sul thread principale. Ciò eliminerebbe tutti i vantaggi dell'uso di NSOperationQueue.

  4. Assicurarsi che l'operazione di "scarico" abbia una priorità inferiore rispetto all'operazione di "caricamento", poiché la priorità è innanzitutto l'esperienza dell'utente, la conservazione della memoria secondo.

  5. Assicurati di mantenere un numero sufficiente di anteprime per almeno una pagina o due avanti e indietro in modo che se NSOperationQueue rimane indietro, si ha un elevato margine di errore prima che le anteprime vuote diventino visibili.

  6. Assicurarsi che l'operazione di caricamento stia solo caricando una miniatura "pre-ridimensionata" e non caricando un'immagine a dimensione intera e riscalando o elaborando. Questo sarebbe un sovraccarico extra nel bel mezzo di un'azione di scorrimento. Vai ancora oltre e assicurati di averli convertiti in PNG16 senza un canale alfa. Ciò darà almeno una riduzione (4: 1) delle dimensioni, con la speranza che non ci siano cambiamenti rilevabili nell'immagine visiva. Considerare anche l'uso di immagini in formato PVRTC che ridurranno ulteriormente le dimensioni (riduzione 8: 1). Ciò ridurrà notevolmente il tempo necessario per leggere le immagini da "disco".

Mi scuso se tutto ciò non ha senso. Non vedo alcun problema con il codice che hai pubblicato e è più probabile che si verifichino problemi nelle implementazioni della tua classe NSOperation o ThumbnailView. Senza rivedere quel codice, potrei non descrivere le condizioni in modo efficace.

Si consiglia di pubblicare il proprio codice NSOperation per il caricamento e lo scaricamento e almeno una parte di ThumbnailView per capire come interagisce con le istanze di NSOperation.

Spero che questo ha contribuito in una certa misura,

Barney

3

Una possibilità, anche se meno visivamente piacevole, è per caricare le immagini solo quando lo scorrimento si arresta.

impostare un flag per disabilitare il caricamento delle immagini in:

-scrollViewWillBeginDragging: 

loading images riattivati ​​quando lo scorrimento smette di usare il:

-scrollViewDidEndDragging:willDecelerate: 

UIScrollViewDelegate metodo. Quando il parametro willDecelerate: è NO, il movimento si è interrotto.

2

il problema è qui:

UIImage *image = [UIImage imageWithContentsOfFile:path]; 

Sembra che filettato o non quando si carica un file dal disco (che forse ciò accade sul thread principale a prescindere, non ne sono assolutamente sicuro) tutto si blocca. Normalmente non lo vedi in altre situazioni perché non hai affatto un'area così ampia in movimento.

1

Mentre la ricerca di questo problema, ho trovato altre due risorse che possono essere di interesse:

Scopri il progetto di esempio iPhone "PageControl": http://developer.apple.com/iphone/library/samplecode/PageControl/index.html

Si carichi pigri visualizzare i controller in un UIScrollView.

  • e -

Scopri i Cocoa Touch lib: http://github.com/facebook/three20 che ha classe un 'TTPhotoViewController' che carica pigri foto/miniature da web/disco.

+0

Grazie per quello. Sicuramente darò un'occhiata. –

0

Impostare shouldRasterize = YES per la vista del contenuto secondario adde alla scrollview. Si è visto per rimuovere il comportamento a scatti della vista di scorrimento creata su misura come un fascino. :)

Effettuare anche un po 'di profiling utilizzando gli strumenti nell'Xcode. Passare attraverso le esercitazioni create per la profilazione di Ray Wenderlich mi ha aiutato molto.