Chargement d'Image optimisé dans un UIScrollView
J'ai UIScrollView qui a un ensemble d'images chargées côte à côte à l'intérieur. Vous pouvez voir un exemple de mon appli ici: http://www.42restaurants.com. Mon problème vient avec l'utilisation de la mémoire. Je tiens à chargement différé des images telles qu'elles apparaissent sur l'écran et décharger les images qui ne sont pas sur l'écran. Comme vous pouvez le voir dans le code, je travaille au minimum sur l'image que je dois charger puis assigner la partie de chargement à une Nsopération et la placer sur une NSOperationQueue. Tout fonctionne très bien à part une expérience de défilement jerky.
Je ne sais pas si quelqu'un a une idée sur la façon dont je peux encore optimiser cela, pour que le temps de chargement de chaque image soit minimisé ou pour que le défilement soit moins saccadé.
- (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: Merci pour la belle des réponses. Voici mon code D'opération et le code ThumbnailView. J'ai essayé plusieurs choses pendant le week-end mais j'ai seulement réussi à améliorer les performances par la suspension de la file d'attente de l'opération pendant le défilement et la reprise lorsque le défilement est terminé.
Voici mes extraits de code:
//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;
}
}
Puis mon charger et décharger des opérations:
- (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];
}
5 réponses
je suis un peu perplexe par le défilement "saccadé". Puisque NSOperationQueue exécute des opérations sur thread(s) séparé (s), je m'attendais au pire à ce que vous voyiez des vues UIImageViews vides apparaître à l'écran.
D'abord et avant tout, je cherche des choses qui ont un impact significatif sur le processeur car la Nsopération seule ne devrait pas interférer avec le thread principal. Deuxièmement, je suis à la recherche de détails concernant la configuration et l'exécution de la Nsopération qui pourrait causer le verrouillage et problèmes de synchronisation qui pourraient interrompre le fil principal et donc avoir un impact sur le défilement.
quelques éléments à considérer:
essayez de charger votre ThumbnailView avec une seule image au début et désactiver la file D'attente Nsopération (sautez tout ce qui suit la vérification "if loaded"). Cela vous donnera une idée immédiate de l'impact du code Nsopération sur les performances.
Gardez à l'esprit que
-scrollViewDidScroll:
peut se produire beaucoup fois au cours d'une seule action de défilement. Selon la façon dont le défilement se déplace et comment votre-centerThumbIndex
is implemented vous pourriez essayer de faire la file d'attente des mêmes actions plusieurs fois. Si vous avez tenu compte de cela dans votre-initWithOperableImage ou-loaded, alors il est possible que votre code provoque ici des problèmes de synchronisation/verrouillage (voir 3 ci-dessous). Vous devez suivre si une Nsopération a été initialisée en utilisant une propriété" atomique " sur L'instance de ThumbnailView. Prévenir les files d'attente une autre opération si cette propriété est définie et ne désactive cette propriété (avec chargée) qu'à la fin des processus de Nsopération.puisque NSOperationQueue opère dans son propre thread(s), assurez-vous qu'aucun de votre code exécuté dans NSOperation ne soit synchronisé ou verrouillé sur le thread principal. Cela éliminerait tous les avantages de l'utilisation du Nsopérationqueue.
assurez-vous que votre opération de "déchargement" a une priorité inférieure à la vôtre opération "load", puisque la priorité est l'expérience de l'utilisateur d'abord, la conservation de la mémoire ensuite.
assurez-vous de garder suffisamment de vignettes pour au moins une page ou deux en avant et en arrière de sorte que si NSOperationQueue tombe en retard, vous avez une marge d'erreur élevée avant que les vignettes Vierges deviennent visibles.
assurez-vous que votre opération de chargement n'est que le chargement d'une vignette "pré-graduée" et ne pas charger une image pleine dimension et le réajustement ou le traitement. Ce serait beaucoup de frais généraux supplémentaires dans le milieu d'une action de défilement. Allez encore plus loin et assurez-vous que vous les avez convertis en PNG16 sans canal alpha. Cela donnera au moins une réduction de taille (4:1) avec, espérons, aucun changement détectable dans l'image visuelle. Envisagez également d'utiliser des images au format PVRTC qui réduiront encore plus la taille (réduction de 8:1). Cela réduit considérablement le temps qu'il faut pour lire les images de "disque".
je m'excuse si tout ce n'est pas logique. Je ne vois aucun problème avec le code que vous avez posté et les problèmes sont plus susceptibles de se produire dans vos implémentations de classe NSOperation ou ThumbnailView. Sans revoir ce code, il se peut que je ne décrive pas les conditions de façon efficace.
Espère que cela a aidé dans une certaine mesure,
Barney
une option, bien que moins agréable visuellement, est de ne charger les images que lorsque le défilement s'arrête.
définir un drapeau pour désactiver le chargement de l'image dans:
-scrollViewWillBeginDragging:
Re-activer le chargement des images lorsque le défilement s'arrête à l'aide de l':
-scrollViewDidEndDragging:willDecelerate:
UIScrollViewDelegate
méthode. Lorsque l' willDecelerate:
paramètre NO
, le mouvement s'est arrêté.
le problème est ici:
UIImage *image = [UIImage imageWithContentsOfFile:path];
il semble que fileté ou pas lorsque vous chargez un fichier à partir d'un disque (ce qui peut se produire sur le fil principal indépendamment, Je ne suis pas totalement sûr) tout décroche. Que vous n'avez normalement pas le voir dans d'autres situations, car vous n'avez pas une grande zone de mouvement du tout.
en étudiant ce problème, j'ai trouvé deux autres ressources qui pourraient être intéressantes:
découvrez l'iPhone exemple de projet "contrôle pagecontrol": http://developer.apple.com/iphone/library/samplecode/PageControl/index.html
il charge paresseusement les contrôleurs de vue dans un UIScrollView.
- et
découvrez le cocoa touch lib: http://github.com/facebook/three20 qui a une 'TTPhotoViewController" classe de farniente à charge des photos/des vignettes à partir de web/disque.
Définir shouldRasterize = OUI pour la sous-vue de contenu adde à la scrollview. Il est vu pour supprimer le comportement saccadé de la vue de rouleau créée sur mesure comme un charme. :)
faites aussi du profilage en utilisant les instruments du Xcode. Passez en revue les tutoriels créés pour le profilage par Ray Wenderlich il m'a beaucoup aidé.