Objet immutable en public, mutable en privé
Je souhaiterais que ma classe ait ceci dans son interface publique (.h):
Par contre, en interne, je veux que places soit un NSMutableArray. Vous comprenez l'idée: l'utilisateur de la classe ne doit pas pouvoir modifier lui-même places, mais passer par les méthodes add et remove.
Comment feriez-vous cela ? Pour l'instant, j'ai un NSMutableArray déclaré dans une catégorie anonyme du .m:
Et je le renvoie ainsi:
(C'est bien ce getter -places qui apparaà®t dans le .h)
Auriez-vous une meilleure idée ?
@property (strong, readonly) NSArray *places;<br />
- (void) addPlace:(Place *)place;<br />
- (void) removePlaceAtIndex:(NSUInteger)placeIndex;
Par contre, en interne, je veux que places soit un NSMutableArray. Vous comprenez l'idée: l'utilisateur de la classe ne doit pas pouvoir modifier lui-même places, mais passer par les méthodes add et remove.
Comment feriez-vous cela ? Pour l'instant, j'ai un NSMutableArray déclaré dans une catégorie anonyme du .m:
@property (strong) NSMutableArray *placesInternal;
Et je le renvoie ainsi:
- (NSArray *) places<br />
{<br />
return placesInternal;<br />
}
(C'est bien ce getter -places qui apparaà®t dans le .h)
Auriez-vous une meilleure idée ?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
.h
@property (strong, readonly) NSArray *places;
et dans le .m
@property (strong, readonly) NSMutableArray *places;
Bonjour,
Au lieu de lui retourner placesInternal, tu crée un nouveau NSArray que tu remplis avec les données de ton NSMutableArray
puis tu le retournes.
Une fois places récupéré, le code client pourrait effectivement appeler les méthodes de NSMutableArray pour ajouter ou retirer des éléments, mais il faudrait caster places en NSMutableArray pour cela.
Dommage, je pensais qu'il y aurait une manière plus élégante de faire.
et dans le .m...
Je vois pas le problème. A moins que je n'ai pas bien compris la question.
On ne peut pas déclarer la propriété comme ça dans le .h:
.h
.m
Dans ce cas l'utilisateur de la classe a tout loisir de pourrir le contenu du tableau.
ça n'empêchera pas l'objet qui demande [toto places] d'agir sur la array à coup de addObject: et cie. Même si le compilo va râler, il pourra
J'ai l'habitude de réécrire le getter comme ceci:
Pourquoi pas très plaisant ? NSMutableArray dérive de NSArray non ? Donc l'utilisation du cast me semble logique, c'est une des bases de la POO...
Et en terme de perf c'est pas forcément super intelligent, parce que selon le nombre d'éléments dans _mutPlaces, tu vas faire des dizaines, centaines, milliers de retain pour rien en pratique (puis de release quand le tableau va être détruit), puisque dans la majorité des cas la solution du cast en NSArray est satisfaisante.
Après, si l'utilisateur de la méthode fait n'importe quoi, tu n'y peux rien. A un moment, il faut faire un compromis et arrêter de vouloir tout blinder, parce que cela à un impact sur les performances.
A part fait tout un tas de retain, puis de release inutiles, qui ont un coût, il n'y a pas de problèmes, effectivement.
Oui ça dépend des cas d'utilisation effectivement. Mais si ce n'est censé être appelé très souvent je trouve ça mieux d'utiliser ce genre de protection.
J'ai souvent été tenté d'utiliser cette sur-protection de simple "peur" qu'on me dise un jour qu'un cast ne suffit pas car, justement, du potentiel risque que la personne derrière s'amuse avec l'objet mutable malgré qu'on ait casté l'objet comme étant immuable. Mais c'est vrai qu'à partir de là .. c'est plus vraiment ma faute...
Du coup vu ce genre d'optimisations que fait Apple sur le sujet, j'en suis à me demander s'il n'y aurait pas un moyen de "convertir un NSMutableArray en NSArray" sans créer de nouvelle instance, juste avec une méthode qui changerait le flag indiquant la "mutability" ou une astuce comme ça... Ce qui résoudrait les 2 problèmes à la fois. Enfin peut-être pas aller jusqu'à ce point là non plus (car si on faisait ça le tableau interne serait aussi rendu immutable, ce qui va poser problème), mais peut-être imaginer que +[NSArray arrayWithArray:] fait du Copy-On-Write ou ce genre de chose, ce qui rendrait l'overhead de créer une nouvelle instance de NSArray bien moins violente, car il n'y aurait alors pas à envoyer de retain à tous les objets du tableau y compris si le tableau contenait un nombre important d'objets...
De plus, le problème de retourner directement le NSMutableArray juste casté en NSArray, c'est que le NS(Mutable)Array peut ensuite changer par la suite (via des méthodes internes à ta classe) et tu auras alors des problèmes. Par exemple imagine que tu t'amuses à faire une boucle sur tes places pour ajouter une copie de chacune dans ton tableau (c'est pour l'exemple hein, on sait pas trop pourquoi
Autre exemple, je veux écrire un test unitaire pour ta méthode, ou encore vérifier si ma place a bien été ajoutée (ou si par exemple elle n'a pas été ajoutée parce que addPlace n'ajoute pas la place si elle existe déjà , imaginons, et que je veux savoir si ça a été le cas) : Bien que quand je lis le code comme ça, rien ne me choque, en pratique ce TestU va toujours foirer, car placesBefore et placesAfter seront exactement la même instance, contrairement à ce que les conventions de nommage laissent penser.
En conclusion, si je suis d'accord qu'il faut éviter d'allouer des instances pour rien quand on peut éviter, en pratique je pense que (surtout si tu actives ARC, en plus) à la fois le compilateur et la façon dont Apple a codé Cocoa et les NSArray, l'overhead ne doit pas être si grand voire doit être fortement réduite par les optimisations, et qu'en contrepartie essayer d'optimiser toi-même risque plus de t'apporter des mauvaises surprises comme celles mentionnées ci-dessus plutôt que de t'apporter des bénéfices. A ce stade je préfère une API sans ambiguité et éviter à celui qui utilisera ma classe de perdre des heures à essayer de débuguer un truc tout ça parce que l'API et les conventions de nommage lui laissent penser un truc qui n'est en pratique pas respecté, plutôt que d'essayer de gagner quelques ms voire ns qui en plus seront déjà réduites par les optimisations du compilateur et de ARC...
Tiens, je n'avais pas songé à cet exemple.
Mais, je crois qu'on arrive là à une limite bien connue de la POO, qui fait que l'abstraction finit par poser problème.
En tout cas, je suis étonné que ma question bien anodine ait fait autant parlé!
dans le machin.h
Et dans le machin.m cela vous parait correcte?
Oups pardon, tu as raison! C'est ma tournée pour la peine!
Autant je suis pas hyper fan de certaines guidelines à l'emporte pièce d'Apple, autant je trouve ça tout à fait louable ici.
Et comme le sous-entend Ali, s'il existe des classes non mutables, c'est pas pour le plaisir du style. Il y a des raisons sous-jacentes d'optimisation de la part d'Apple.
Bref, oui ça a un coût sur le retour de méthode mais de là à dire que c'est inutile...
Il lui faut en privé un NSMutableArray car il lui faut pouvoir modifier le contenu du tableau, pour y ajouter ou supprimer des éléments.
Le NSMutableArray lui peut tout à fait rester en readonly, même en private, car on ne va allouer l'instance qu'une seule fois, c'est son contenu qu'on va modifier. Pas besoin qu'il soit readwrite pour ça. C'est donc un problème différent
Donc il suffit juste d'écrire dans Machin.h :
Et dans le Machin.m :
Pas besoin de synthesize, ni de ivar, etc... juste redéfinir la @property qu'on a mis en readonly dans le .h en readwrite dans le .m, c'est tout.
Mais bon, comme je le disais plus haut ça ne répond pas à la problématique de Céroce qui est un peu différente...
Une NSString en assign, c'est forcément incorrect.
Décidément, à part le faire de redéfinir la property(readonly) en (readwrite), qui est finalement le seul truc à faire, tout le reste de l'exemple n'est pas vraiment très clean...
Ok, ne pas suivre les exemples du bouquin: Le guide de survie Objective-C 2.0 publié en 2010. (pearson)
Question subsidiaire:
Si je programme "à l'ancienne", sans properties et en déclarant normalement mes variables, à part le fait que certaines vérifications ne seront pas faites, quel sera vraiment le risque?
Si tu ne vois pas le souci, va très très vite réviser les bases de la gestion mémoire en Objective-C (que ce soit avec ou sans ARC d'ailleurs, le pb est le même), en particulier la différence entre assign ou weak et retain/strong !!!
La liste est longue, mais pour résumer tu risques :
PS : Pourquoi as-tu barré "iOS" dans ma citation précédente ? Je réitère ce que j'ai dit, depuis le début on a jamais eu besoin de déclarer les variables d'instance sous iOS. Jamais. Ceci est vrai depuis le "Modern Runtime", mais le Legacy Runtime qui existait avant n'était utilisé que par OSX 32 bits avant la sortie de iOS. Quand iOS est sorti, dès sa première version, il tournait déjà avec le Modern Runtime. (Et quand ils ont sorti OSX 64 bits, il est aussi passé au Modern Runtime). Ce qui fait que même depuis les tout débuts d'iOS, on n'a vraiment jamais eu besoin de déclarer les variables d'instance correspondantes aux backing variables des @property, dès qu'on écrivait une @property et un @synthesize, la variable d'instance était générée automatiquement. (Et depuis peu avec les dernières versions du compilateur y'a mm plus besoin de @synthesize, mais ça par contre c'est assez récent)