2009-07-08 4 views
16

Ich habe ein UIScrollView, in dem eine Reihe von Bildern Seite an Seite geladen ist. Sie können ein Beispiel meiner App hier sehen: http://www.42restaurants.com. Mein Problem tritt bei der Speichernutzung auf. Ich möchte die Bilder langsam laden, während sie auf dem Bildschirm erscheinen, und Bilder entladen, die nicht auf dem Bildschirm sind. Wie Sie im Code sehen können, erarbeite ich mindestens das Bild, das ich laden muss, und ordne dann den Ladebereich einer NSOperation zu und platziere sie auf einer NSOperationQueue. Alles funktioniert gut, abgesehen von einem ruckartigen Scroll-Erlebnis.Optimiertes Laden von Bildern in ein UIScrollView

Ich weiß nicht, ob jemand irgendwelche Ideen hat, wie ich das noch optimaler machen kann, damit die Ladezeit jedes Bildes minimiert wird oder damit das Scrollen weniger ruckelt.

- (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: Vielen Dank für die wirklich großen Antworten. Hier ist mein NSOperation-Code und ThumbnailView-Code. Ich habe ein paar Dinge über das Wochenende ausprobiert, aber ich habe es nur geschafft, die Leistung zu verbessern, indem ich die Arbeitswarteschlange während des Scrollens aufgehalten und wieder aufgenommen habe, wenn das Scrollen beendet ist.

Hier sind meine Code-Schnipsel:

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

meine Last Dann und entladen Operationen:

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

Antwort

15

Ich bin ein wenig verwirrt durch das "ruckelnde" Scrollen. Da NSOperationQueue Operationen auf separaten Threads ausführt, hätte ich im schlimmsten Fall erwartet, dass auf dem Bildschirm leere UIImageViews angezeigt werden.

In erster Linie würde ich nach Dingen suchen, die den Prozessor erheblich beeinträchtigen, da NSOperation allein den Hauptthread nicht stören sollte. Zweitens würde ich nach Details suchen, die das Setup und die Ausführung von NSOperation betreffen, die Sperr- und Synchronisierungsprobleme verursachen könnten, die den Hauptthread unterbrechen und daher das Scrollen beeinträchtigen könnten.

ein paar Punkte zu beachten:

  1. Versuchen Sie ThumbnailView, mit einem einzelnen Bild zu Beginn geladen und die NSOperation Warteschlangen zu deaktivieren (nur alles nach dem Sprung „wenn geladen“ check Dies wird Ihnen ein. unmittelbare Idee, ob die NSOperation Code Leistung auswirkt.

  2. beachten Sie, dass -scrollViewDidScroll:viel mal im Verlauf einer einzigen Scroll-Aktion erfolgen kann. Je nachdem, wie für die Scroll bewegt und wie Sie Ihre -centerThumbIndex ist Sie versuchen möglicherweise, die gleichen Aktionen mehrere Male in die Warteschlange zu stellen.Wenn Sie dies in Ihrem -initWithOperableImage oder -loaded berücksichtigt haben, verursacht Ihr möglicher Code hier Probleme mit der Synchronisierung/Sperre (siehe 3 unten). Sie sollten verfolgen, ob eine NSOperation mit einer "atomaren" Eigenschaft in der ThumbnailView-Instanz initiiert wurde. Verhindern Sie das Einreihen in eine andere Operation, wenn diese Eigenschaft festgelegt ist, und deaktivieren Sie diese Eigenschaft (zusammen mit dem Laden) nur am Ende der NSOperation-Prozesse.

  3. Da NSOperationQueue in seinen eigenen Threads arbeitet, vergewissern Sie sich, dass keiner Ihrer Code, der innerhalb der NSOperation ausgeführt wird, mit dem Hauptthread synchronisiert oder synchronisiert wird. Dies würde alle Vorteile der Verwendung der NSOperationQueue beseitigen.

  4. Stellen Sie sicher, dass Ihre "Entladen" -Operation eine niedrigere Priorität als Ihre "Laden" -Operation hat, da die Priorität der Benutzererfahrung zuerst ist, Speichererhaltung Sekunde.

  5. Achten Sie darauf, dass Sie mindestens eine Seite oder zwei Vor- und Rückseiten genügend Thumbnails behalten, so dass bei einem Fehler von NSOperationQueue eine hohe Fehlerquote besteht, bevor leere Thumbnails sichtbar werden.

  6. Stellen Sie sicher, dass Ihre Ladeoperation nur eine "vorskalierte" Miniaturansicht lädt und kein Bild in voller Größe lädt und die Skalierung oder Verarbeitung nicht durchführt. Dies wäre eine Menge zusätzlicher Overhead während einer Scroll-Aktion. Gehen Sie noch weiter und stellen Sie sicher, dass Sie sie ohne Alphakanal in PNG16 konvertiert haben. Dies wird mindestens eine (4: 1) Verringerung der Größe mit hoffentlich keine erkennbare Änderung in dem visuellen Bild ergeben. Denken Sie auch daran, Bilder im PVRTC-Format zu verwenden, die die Größe noch weiter reduzieren (8: 1-Reduzierung). Dies wird die Zeit, die zum Lesen der Bilder von "Platte" benötigt wird, stark reduzieren.

Ich entschuldige mich, wenn dies nicht sinnvoll ist. Ich sehe keine Probleme mit dem von Ihnen geposteten Code, und Probleme treten eher in Ihren NSOperation- oder ThumbnailView-Klassenimplementierungen auf. Ohne diesen Code zu überprüfen, beschreibe ich die Bedingungen möglicherweise nicht effektiv.

Ich würde empfehlen, Ihren NSOperation-Code zum Laden und Entladen und mindestens genug ThumbnailView zu veröffentlichen, um zu verstehen, wie er mit den NSOperation-Instanzen interagiert.

Hoffnung, dies zu einem gewissen Grad geholfen,

Barney

3

Eine Option, wenn auch weniger optisch ansprechend, nur Bilder laden, wenn das Scrollen stoppt.

ein Flag gesetzt Bildlade in deaktivieren:

-scrollViewWillBeginDragging: 

Erneutes Aktivieren Laden von Bildern, wenn das Scrollen stoppt die Verwendung:

-scrollViewDidEndDragging:willDecelerate: 

UIScrollViewDelegate Methode. Wenn der Parameter willDecelerate:NO ist, wurde die Bewegung gestoppt.

2

das Problem ist hier:

UIImage *image = [UIImage imageWithContentsOfFile:path]; 

Es scheint, dass Gewinde- oder nicht, wenn Sie von der Festplatte eine Datei laden (was vielleicht das passiert auf den main thread egal, ich bin mir nicht ganz sicher) alles stallt. Normalerweise sieht man das nicht in anderen Situationen, weil man sich nicht so groß bewegt, wenn überhaupt.

1

Während dieses Problem untersucht, fand ich zwei weitere Ressourcen, die von Interesse sein könnte:

Schauen Sie sich das iPhone Beispielprojekt „Pagecontrol“: http://developer.apple.com/iphone/library/samplecode/PageControl/index.html in einem UIScrollView

Es faul Lasten View-Controller.

  • und -

prüft den Kakao Touch lib aus: http://github.com/facebook/three20, die ein 'TTPhotoViewController' Klasse, dass faule Last Fotos/thumbnails von web/Scheibe hat.

+0

Danke dafür. Ich werde auf jeden Fall einen Blick darauf werfen. –

0

Legen Sie shouldRasterize = YES für die Sub-Content-Ansicht adde zum scrollview fest. Es wird gesehen, um das ruckartige Verhalten von benutzerdefinierten Scroll-Ansicht wie ein Charme zu entfernen. :)

Auch einige Profiling mit den Instrumenten in der Xcode. Gehen Sie durch die Tutorials erstellt für Profiling von Ray Wenderlich es hat mir sehr geholfen.