2010-10-27 24 views
4

J'ai rencontré un problème iOS intéressant impliquant un CATiledLayer. Cela s'est seulement produit sur l'appareil - pas dans le simulateur.CATiledLayer drawInContext appelé après la vue associée est parti

Ma vue dessine son CALayer via drawLayer: inContext: délégué de rappel. Cette couche possède une sous-couche dérivée de CATiledLayer, qui effectue son propre dessin dans une méthode drawInContext: substituée.

Les deux couches rendent le contenu PDF via CGContextDrawPDFPage(). (Le CALayer dessine une version basse résolution, tandis que la sous-couche CATiledLayer dessine un contenu haute résolution par dessus.)

J'ai rencontré un scénario dans lequel j'aurais fini avec la vue - je l'enlèverais de sa vue d'ensemble et la lâcherais. . dealloc() est appelé sur la vue. Quelque temps plus tard, la méthode drawInContext: de CATiledLayer serait appelée (sur un thread d'arrière-plan), par le système. Cela dessinerait, mais au retour de la méthode, Springboard tomberait en panne et, ce faisant, ferait tomber mon application.

Je l'ai corrigé en définissant un drapeau dans le CATiledLayer, en lui disant de ne plus rendre, à partir de la méthode dealloc de la vue.

Mais je peux seulement imaginer qu'il y a une manière plus élégante. Comment se fait-il que la méthode CATiledLayer drawInContext: soit encore appelée après la couche parente, et que la vue de la couche parente ait été désallouée? Quelle est la bonne façon de fermer la vue afin que cela n'arrive pas?

Répondre

0
-(void)drawLayer:(CALayer *)calayer inContext:(CGContextRef)context {  
    if(!self.superview) 
     return; 
    ... 

MISE À JOUR: Si je me souviens, il y avait des problèmes avec ce dans les anciennes versions d'iOS quand il est venu à CATiledLayers, mais établit le délégué à zéro avant dealloc est maintenant le chemin à parcourir. Voir: https://stackoverflow.com/a/4943231/2882

+0

Cela a fonctionné excellent pour moi, simple et efficace. –

+4

Si la vue est déjà désallouée, comment self.superview (un accès à self, un objet désalloué) peut-il fonctionner? –

3

Définissez view.layer.delegate sur zéro avant de libérer la vue.

+0

Je le fais déjà. Merci. – TomSwift

+0

Remarque: le commentaire de TomSwift concerne la suppression du délégué dans dealloc. C'est en effet trop tard, et la réponse a été éditée pour que le délégué soit nul * avant * de libérer la vue. – Danra

9

La lenteur, mais la meilleure façon de corriger est également de définir view.layer.contents = nil. Cela attend la fin des threads.

+1

Peter a raison, c'est le seul moyen fiable de le faire. Il n'est pas documenté, mais j'ai écrit un test pour confirmer qu'il bloque effectivement jusqu'à ce que tous les appels de délégué de fond soient terminés. Définir le délégué à zéro (la réponse acceptée ci-dessus) ne fait rien si les tâches de dessin sont déjà mises en file d'attente dans GCD. – Crufty

0

J'ai passé pas mal de temps là-dessus. Ma dernière approche consiste à déclarer une variable de bloc et à l'affecter à self dans la méthode viewWillDisappear. Appelez ensuite l'appel de setContents dans une file d'attente de répartition globale - inutile de verrouiller le thread principal. Ensuite, lorsque setContents renvoie invoquer sur le thread principal et définir la variable de bloc à zéro, ce qui devrait assurer que le contrôleur de vue est libéré sur le thread principal. Un avertissement cependant, j'ai trouvé qu'il est prudent d'utiliser dispatch_after pour l'invocation sur le thread principal car la file d'attente globale conserve le contrôleur de vue jusqu'à ce qu'il sorte de son bloc, ce qui signifie que vous pouvez avoir une condition de concurrence entre le contrôleur de vue) et le bloc de thread principal définissant la variable de bloc à zéro) ce qui pourrait conduire à une désallocation sur le thread de file d'attente de répartition globale.