2010-07-20 5 views
2

J'utilise un tableau d'emballages ASIHTTPRequest (AsyncImageLoader) pour télécharger des images pour les cellules dans un UITableView.ASIHTTPRequest dealloc et problème EXC_BAD_ACCESS

Je vais avoir des problèmes de manipulation de vie ASIHTTPRequests. Si je les libère, je finirai par avoir un EXC_BAD_ACCESS si je continue à défiler de haut en bas pendant qu'ils essaient de charger des images.

Voici à quoi ressemble mon emballage. self.request a conserver la propriété, targetCell est la cellule que je veux mettre l'image:

@implementation AsyncImageLoader 
- (void)loadImageFromURL:(NSString*)fileName target:(ResultTableViewCell*)targetCell { 
    // check for cached images, etc 

    NSURL* url = [NSURL URLWithString:fileName]; 
    [self.request cancel]; 
    self.request = [ASIHTTPRequest requestWithURL:url]; 
    self.request.delegate = self; 
} 

- (void)startLoading { 
    [self.request startAsynchronous]; 
} 

- (void)cancel { 
    [self.request cancel]; 
    self.request = nil; 
} 

- (void)requestFinished:(ASIHTTPRequest*)requestDone { 
    // cache image, set cell image... 

    self.request = nil; 
} 

- (void)requestFailed:(ASIHTTPRequest*)requestDone { 
    // handle answer as well 

    self.request = nil; 
} 
@end 

loadImageFromURL est appelé à cellForRowAtIndexPath pour la indexPath.row % 6 e AsyncImageLoader, donc si je continue à défiler vers le haut et vers le bas, il est appelé encore et encore avec le même objet, en annulant les demandes car elles ne sont pas encore terminées.

Mais je finis toujours par avoir un EXC_BAD_ACCESS. Selon la pile d'appel, il se produit dans ASIHTTPRequest de markAsFinished, appelé par failWithError, appelé par [self.request cancel] dans loadImageFromURL:. La plupart du temps, la requête ASIHTTPRequest était déjà publiée (j'ai ajouté un NSLog dans son dealloc), mais je ne vois pas comment cela est possible car le message ASIHTTPRequest semble se retenir pour ne pas être libéré lors de l'annulation.

Je n'ai aucun EXC_BAD_ACCESS si je supprime self.request = nil dans les méthodes de délégué, mais comme ASIHTTPRequests continue à être créé sans deallocation, ils ne fonctionnent pas du tout.

Quelqu'un pourrait-il me dire ce que je fais mal?

+0

Vous appelez annuler sur un objet nul, c'est ce que vous faites mal. Comme vous l'avez constaté vous-même, définir la demande sur nil dans les méthodes delegate n'est pas une bonne idée: l'emplacement correct est dans - (void) viewDidUnload –

Répondre

1

Il semble que ASIHTTPRequest (ou peut-être iOS) soit un peu instable lors de l'annulation de requêtes, en particulier lorsqu'il y a plusieurs requêtes et que beaucoup sont annulées et libérées assez rapidement.

En tant que solution, j'ai abandonné la conception de créer de nombreuses demandes et de les annuler si elles ne sont plus nécessaires. J'ai fait un wrapper stockant ASIHTTPRequest s dans un NSOperationQueue, qui filtre également les URL de sorte que la même requête ne soit pas démarrée deux fois (dans le cas où une cellule apparaît, puis disparaisse avant que l'image soit entièrement chargée, et s'affiche à nouveau). Je stocke tous les résultats de la requête (images) dans un cache. Il en résulte une activité réseau peut-être légèrement plus élevée, mais tout le reste est plus clair à chaque nouvelle requête (même si l'image n'est pas affichée) et mise en cache, donc pas de jonglage avec création/annulation/etc. Et par conséquent plus de crash.

5

Lorsque vous exécutez dans un problème où vous EXC_BAD_ACCESS cela signifie que vous avez quelque chose sur Desserré. Dans la plupart des cas, vous devez simplement exécuter le débogueur (Command-Y) et voir la dernière ligne de votre code dans la trace de la pile lorsqu'il se bloque pour savoir quel objet est en cours de relâchement. Si cela ne fonctionne pas, alors vous devez turn on zombies et voir quel objet est à nouveau consulté après qu'il a été libéré. Cela vous pointera presque toujours dans la bonne direction.

En note, il y a quelques autres bibliothèques qui font ce que vous essayez de faire. Je n'ai pas beaucoup utilisé celui-ci, mais on dirait qu'il a du potentiel au moins. Départ: SDWebImage. Peut-être que cela vous donnera quelques idées.

Cordialement.

+0

Merci pour votre réponse. J'ai activé Zombie et il a souligné que le problème est dû à un appel à une instance désallouée de ASIHTTPRequest.Je ne vois pas pourquoi cela se produit, car une seule conservation et libération est effectuée dans AsyncImageLoader, et ASIHTTPRequest devrait se conserver suffisamment pour fonctionner correctement. La raison peut être que lorsque j'appelle [self.request cancel] dans loadImageFromURL, self.request est déjà libéré, mais il doit être défini sur nil car les méthodes requestFinished/Failed ont été appelées dans le thread principal. – Jukurrpa

+0

C'est difficile à dire, mais je pense que vous devrez probablement faire un peu plus de factorisation. Voici ce que je veux dire. Que se passe-t-il si vous faites défiler la vue de la table alors que la demande en cours est toujours en attente? Vous avez passé dans une cellule de vue de table. Je suppose que vous allez remplir une image une fois le téléchargement terminé, mais si l'utilisateur a maintenant défilé, l'image que vous souhaitez afficher ne sera plus la bonne. Il serait préférable de transmettre un délégué que vous pouvez notifier lorsque l'image est disponible. Ce délégué peut ensuite déterminer s'il doit toujours remplir la cellule actuelle avec cette image. –

+0

En d'autres termes, mon point est que vous devez reconsidérer votre conception un peu qui peut très bien résoudre ce problème intentionnellement ou non. –

2

Cela ressemble beaucoup à un accident que je vois.

Je ne suis pas encore sûr de la cause, mais je crois fermement qu'il n'est pas lié au code client (c'est-à-dire qu'il est dans ASIHTTPRequest ou le système d'exploitation).

J'ai corrigé quelques problèmes dans la requête http asi, mais ce crash reste.

J'ai essayé d'obtenir de l'aide sur les forums de pommes ici:

https://devforums.apple.com/thread/59843?tstart=0

Voici ce que l'accident ressemble pour moi:

#2 0x32c02e14 in CFRetain() 
#3 0x32c709b6 in __CFTypeCollectionRetain() 
#4 0x32c06c60 in _CFArrayReplaceValues() 
#5 0x32b0994c in -[NSCFArray insertObject:atIndex:]() 
#6 0x32b098f0 in -[NSCFArray addObject:]() 
#7 0x32b709f0 in __chooseAll() 
#8 0x32b71bde in __finishedOp() 
#9 0x32b26636 in +[NSOperation observeValueForKeyPath:ofObject:change:context:]() 
#10 0x32b265aa in NSKVONotify() 
#11 0x32b13306 in -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:]() 
#12 0x0007d6ec in -[ASIHTTPRequest markAsFinished] (self=0x5a7cc40, _cmd=0xa1710) at ASIHTTPRequest.m:2817 
#13 0x00077e02 in -[ASIHTTPRequest failWithError:] (self=0x5a7cc40, _cmd=0x330cdfc4, theError=0x248130) at ASIHTTPRequest.m:1708 
#14 0x000712dc in -[ASIHTTPRequest cancel] (self=0x5a7cc40, _cmd=0x322b4298) at ASIHTTPRequest.m:515 
#15 0x0008884c in -[UIHTTPImageView setImageWithURL:placeholderImage:] (self=0x5a7c9d0, _cmd=0xa3f0c, url=0x5aa7760, placeholder=0x0) at UIHTTPImageView.m:21 
#16 0x0006183c in -[MeetingView tiledScrollView:tileForRow:column:resolution:] (self=0x5a32560, _cmd=0x9bfac, tiledScrollView=0x5a74570, row=1, column=0, resolution=0) at MeetingView.m:1053 
#17 0x0000475e in -[TiledScrollView layoutSubviews] (self=0x5a74570, _cmd=0x32299680) at TiledScrollView.m:181 
#18 0x31515f32 in -[UIView(CALayerDelegate) _layoutSublayersOfLayer:]() 
#19 0x32c29ffa in -[NSObject performSelector:withObject:]() 
#20 0x30964798 in -[CALayer layoutSublayers]() 
#21 0x30964568 in CALayerLayoutIfNeeded() 
#22 0x30959b62 in CA::Context::commit_transaction() 
#23 0x3095997a in CA::Transaction::commit() 
#24 0x3097f164 in CA::Transaction::observer_callback() 
#25 0x32c702a0 in __CFRunLoopDoObservers() 
#26 0x32c23bb0 in CFRunLoopRunSpecific() 
#27 0x32c234e0 in CFRunLoopRunInMode() 
#28 0x30d620da in GSEventRunModal() 
#29 0x30d62186 in GSEventRun() 
#30 0x314d54c8 in -[UIApplication _run]() 
#31 0x314d39f2 in UIApplicationMain() 
#32 0x0000245c in main (argc=1, argv=0x2ffff5b4) at main.m:14 

D'après ce que je peux dire, le les actions ressemblent à ceci:

  1. La requête est créée et démarrée, et donc ajouté à la file d'attente
  2. demande est annulée avant qu'elle ne commence à courir
  3. demande est annulée avec succès, retiré de la file d'attente et désallouée
  4. Une autre demande complète, et la file d'attente que les appels à retenir sur la demande désallouées

À titre expérimental, j'ai essayé de conserver les requêtes une fois de plus - cela arrête le plantage, mais la file d'attente s'arrête.

Il est possible que ASIHTTPRequest continue à mal gérer la demande d'annulation, mais je trouve difficile de voir comment.

Mise à jour

Cela devrait corriger:

http://github.com/jogu/asi-http-request/commit/887fcad0f77e9717f003273612804a9b9012a140

mieux que je peux dire, le NSOperationQueue était d'entrer dans un mauvais état après avoir été informé qu'une demande qui n'a pas commencé avait terminé. Répondre à moi-même comme une synthèse de ma conclusion et d'autres réponses.

+0

Salut, je suis également assez sûr que ce bug provient de ASIHTTPRequest (ou peut-être iOS). La chaîne d'actions que vous avez listée est exactement la même dans ma situation, donc je crois que l'annulation de requête peut être instable. Comme l'a conseillé Matt Long, j'ai plutôt cherché un autre design (voir ma réponse). – Jukurrpa

+0

Je pense que c'est ASIHTTPRequest, je viens de mettre à jour ma réponse avec un lien vers un correctif, si vous avez envie de l'essayer. – JosephH

0

essayer d'ajouter quelque chose comme ça dans le dealloc de votre point de vue:

[self.thumbnailRequest cancel]; 
self.thumbnailRequest.delegate = nil; 
[self.thumbnailRequest setDidFinishSelector:NULL]; 
[self.thumbnailRequest setDidFailSelector:NULL]; 
self.thumbnailRequest = nil;