samedi 17 mars 2012

Outlets, Cocoa Cocoa Touch vs

J'ai presque toujours suivre l'exemple d'Apple sur le cacao et les conventions Cocoa Touch. Je suppose que par le temps à l'extérieur des développeurs comme moi voir quelque chose pour la première fois, les ingénieurs d'Apple ont été vivre avec cette chose pendant de nombreux mois, alors qu'ils ont probablement obtenu une bien meilleure idée que moi sur la meilleure façon d'utiliser cette nouvelle chose.

Mais, après avoir passé du temps avec leurs trucs, parfois - pas souvent, mais parfois - je suis en désaccord avec ce qui semble être d'Apple recommande «meilleures pratiques» pour faire quelque chose. Je pense que je suis venu à la décision que le comportement IBOutlet dans iOS est l'un de ces domaines.

Si vous regardez des extraits de documentation d'Apple et des exemples de code, vous constaterez qu'ils conservent presque toujours propriétés IBOutlet, comme:

 #import <UIKit/UIKit.h>   @interface FooView : UIView  {  } @synthesize (nonatomic, retain) IBOutlet UIButton button; @synthesize (nonatomic, retain) IBOutlet UITextField textField; @synthesize (nonatomic, retain) IBOutlet UIImageView imageView; @end 


Il ya une bonne raison à cela. Dans iOS, la documentation stipule explicitement que vous devez conserver tous les points car le chargeur bundle pour iOS autoreleases tous les objets créés à la suite du chargement d'une plume.
Les objets dans le fichier nib sont créés avec un nombre de 1 conservent puis autoreleased. Comme il reconstruit la hiérarchie d'objets, cependant, UIKit rétablit les connexions entre les objets en utilisant l'setValue: forKey: méthode, qui utilise la méthode setter disponible ou conserve l'objet par défaut si aucune méthode setter est disponible. Si vous définissez des débouchés pour nib fichier des objets, vous devez toujours définir une méthode setter (ou biens déclarés) pour accéder à cette sortie. Méthodes setter de débouchés devraient conserver leurs valeurs, et les méthodes setter pour les points contenant des objets de haut niveau doivent conserver leurs valeurs pour les empêcher d'être libéré. Si vous ne stockez pas les objets de haut niveau dans les points, vous devez conserver soit le tableau renvoyé par la loadNibNamed: le propriétaire: options: méthode ou les objets à l'intérieur du tableau pour éviter ces objets d'être libérés prématurément.

Ceci est différent de cacao sur le Mac, où il n'était pas nécessaire de conserver les débouchés et les gens ont rarement fait. En fait, nous n'avons généralement pas s'embêter avec des méthodes d'accès ou de mutator pour les points (il était juste en tapant supplémentaires inutiles dans la plupart des cas), nous venons de mettre le mot-clé IBOutlet en face de la variable d'instance et le chargeur nib était heureux de joindre nos points de vente comme ça, en conservant les objets qui avaient besoin de soutènement.

Le comportement sous Cocoa / Mac n'est pas réellement de conserver tout dans la plume, mais plutôt de conserver tous les objets qui n'a pas un objet parent de le conserver. Donc, en d'autres termes, si un objet dans une plume sera retenue par autre chose, comme un SuperView, le chargeur de nib ne dérange pas de le conserver encore. Mais, si elle n'a pas, le chargeur bundle il conserve afin qu'il ne soit pas libéré.

C'est une approche logique et, en fait, était nécessaire de retour dans le 2.0 de pré-Objective-C jours parce prises à l'époque étaient juste Ivars et il n'y avait pas de moyen facile pour la classe contrôleur de conserver des objets qui devaient être conservés.

Je me demande pourquoi ils changer le comportement fondamental d'un objet de fondation comme NSBundle entre Mac OS et iOS? NSBundle ne fait pas partie du cacao ou de Cocoa Touch, ça fait partie de la Fondation, et le point entier de la Fondation est d'avoir des objets communs entre les différents systèmes d'exploitation.

J'ai écrit une petit projet pour tester si le chargeur Bundle vraiment se comporter différemment, tel que documenté, en utilisant une instance de UIView dans le bec sans SuperView. Evidemment, lorsque je n'ai pas de conserver la prise, soit je suis une EXC_BAD_ACCESS ou un objet tout à fait différent quand j'ai imprimé la prise d'NSLog (). La différence est réelle. Le chargeur de botte sur le Mac conservera points pour vous si ils ont besoin pour être conservé, ce qui vous permet de continuer à utiliser des variables d'instance, ou des propriétés avec le mot clé attribuer. Cela signifie que vous n'avez pas à communiquer vos points de vente dans dealloc et vous n'avez pas à perdre son temps avec quelque chose comme viewDidUnload sur iOS.

Le chargeur de paquet sur l'iOS, d'autre part, ne conserve rien pour vous, alors si un objet n'est pas un objet parent de le conserver, vous devez le conserver dans votre classe contrôleur ou vous allez vous retrouver avec un invalide sortie.

Je ne vois vraiment pas la valeur à changer ce comportement. Je devine que la décision a été prise pour des raisons d'efficacité de la mémoire dans les premiers jours de l'iPhone. L'idée étant que vous pouvez charger une plume avec des instances d'objets que vous ne sont pas réellement à l'aide, et avec l'ancien comportement, ceux qui prendraient la mémoire aussi longtemps que la plume a été chargé. Cela ne veut pas nécessairement ressembler à une bonne idée sur un appareil embarqué sans mémoire virtuelle et 128 Mo de RAM, ce qui est de l'iPhone original et l'iPhone 3G avait.

Malgré cela, je pense que le remède est ici pire que le mal. Si vous ne vous souvenez de libérer vos points de vente dans les viewDidUnload (qui, si je me souviens bien, nous ne pouvions même pas faire dans la version 2.0 du SDK), vos points de vente continueront à utiliser de la mémoire après la plume est déchargé, ce qui évite aucun avantage de l'lazy loading. Essentiellement, il est plus fragile car il repose sur le programmeur fait la bonne chose et il ya peu, voire aucun des situations où un programmeur aurait besoin pour ne pas faire la bonne chose.

En vertu de la chargeuse bundle ne conservant pas prises, elle exige aussi plus par cœur le code réutilisable, qui sera écrit dans chaque classe contrôleur dans chaque application iOS, mais il fonctionne aussi bien d'un risque d'utilisation de la mémoire inutile, sans doute un plus grand risque. En d'autres termes, le remède n'est pas meilleur que la maladie.

iPhones sont de plus robuste et moins de mémoire limitée avec chaque nouvel appareil qui sort. Je dirais que c'est déjà l'heure (ou, à tout le moins, sera bientôt le temps), pour amener le comportement des deux chargeurs bundle ensemble. S'ils sont réunis, ils devraient être réunis en utilisant le comportement de cacao vieille pas le comportement de nouveaux iOS. Quand vous pensez au nombre de personnes codant pour l'iOS maintenant, ces dealloc supplémentaire requis et les lignes viewDidUnload dans chaque classe unique contrôleur de chaque application iOS sont vraiment ajouter jusqu'à beaucoup d'heures d'ingénierie ont perdu sur le passe-partout.

Il ya quelques semaines, j'ai commencé à expérimenter avec l'aide de céder au lieu de le conserver pour IBOutlets sauf dans les cas où l'objet du point de vente n'a pas eu une superview ou un autre objet à son maintien. Si une prise n'est pas une vue ou un contrôle, alors j'utilise aussi conserver. En substance, je suis imitant l'ancien comportement de la plume dans la conception de mes classes de contrôleur.

Cela a conduit à une frappe beaucoup moins et moins de code à maintenir parce que 95% ou plus des points de vente que je crée sont connectés à des objets retenus par leurs superview pendant l'existence entière de la classe contrôleur.

Maintenant, je ne suis pas nécessairement dire que vous devriez faire ce que je fais. Il peut être difficile, parfois, se souvenant de laquelle les objets doivent être conservés et qu'ils peuvent être difficiles à déboguer, si vous vous trompez. Apple a fait une recommandation de bonnes raisons et je ne pense pas que vous devriez ignorer cette recommandation à la légère. Cela étant dit, si vous êtes suffisamment à l'aise avec la gestion de la mémoire en Objective-C et le chargeur bundle pour être en mesure de distinguer si un objet nib sera automatiquement retenue par autre chose, vous pourriez vous épargner un peu juste de taper dans le temps.

Normalement, je essayer de tenir compte des changements d'Apple, mais aussi dans ce cas, je ne peux pas me convaincre que c'était un bon changement. Le comportement nib anciens de conserver les seules choses qui ont besoin de retenue a été en usage depuis plus de 20 ans, qui remonte à quand les ordinateurs de bureau sont moins puissants que nos iPhones sont, et il ne semble pas être un avantage pratique pour le changement. D'autre part, nous serions tous bénéficier de revenir à l'ancien comportement de Mac OS bundle parce que nous aurions moins de rendre le travail à faire lors de la création d'une classe contrôleur. Il ya aussi peu de danger à changer ce comportement, car code qui suit les recommandations actuelles d'Apple continuerait de fonctionner correctement.

Aucun commentaire: