2010-10-12 9 views
1

Donc, supposons que vous ayez une variable locale NSArray * myArray déclarée dans votre fichier d'en-tête de classe.iPhone - initialisation des variables en utilisant self

Vous écrivez ensuite @property (nonatomic, retain) NSArray *myArray également dans votre fichier d'en-tête.

Dans votre fichier .m, vous écrivez .

Tous très standard jusqu'à présent. Vous avez maintenant une variable myArray, qui peut être accessible via les setters et les getters synthétisés par Apple.

Un peu plus tard, vous initialisez votre variable.

NSArray *anArray = [[NSArray alloc] initWithObjects etc etc...]; 
self.myArray = anArray; 
[anArray release]; 

Alors maintenant monTableau pointe vers un tableau en mémoire, ce qui a un compte de libération d'un (si je ne me trompe pas).

Ma question est, pourquoi ne pouvons-nous écrire

@property (nonatomic, assign) NSArray *myArray; 
@synthesize myArray; 

..et puis par écriture d'initialisation

self.myArray = [[NSArray alloc] initWithObjects etc etc...]; 

Cela a TOTALEMENT confondu moi depuis la première fois que je l'ai vu. Y a-t-il une raison technique à cela? Ou moral? ;-) Ou théorique?

Toute aide serait très apprécié ...

Vive

Karl ...

Répondre

0

Vous pouvez certainement. L'utilisation de propriétés "assign" à la place des propriétés "retain" est en fait une pratique courante (voir quelques exemples d'en-têtes d'objets de base d'Apple pour des exemples). Le problème ici est que votre code est conscient de cette relation de mémoire (si la propriété a quelque chose dedans à un moment donné).

Certains programmeurs préfèrent ce modèle, en fait. Contrôle personnel complet de la mémoire. J'ajouterais, cependant, que c'est un modèle très difficile à protéger quand il y a plusieurs développeurs sur un projet sauf s'ils sont tous les types qui aiment gérer manuellement la mémoire.Il est beaucoup plus facile de fuir la mémoire dans ce modèle à partir d'un simple oubli et les compilateurs ont plus de mal à interroger de tels problèmes.

+0

Merci, c'est ce que je pensais. Il semblerait que les seules raisons de ne pas faire cela sont celles que vous avez mentionnées (difficulté de gestion etc), mais techniquement, les deux sont identiques. Enfin, je peux dormir la nuit :-) –

+0

Cela efface vraiment les problèmes techniques avec la technique. Ce n'est pas seulement un cas de "quand il y a plusieurs développeurs". Il y a toujours au moins deux partis au travail dans votre code - vous et Apple. Et Apple a rendu ses directives de gestion de la mémoire très explicites. – Chuck

+0

Les directives ne sont que des lignes directrices, pas des erreurs de programmation. – hotpaw2

0

Il n'y a aucune raison pour laquelle vous ne pouvez pas faire cela. Vous devez juste faire attention à votre mémoire. Parce que que se passe-t-il lorsque vous affectez plus tard à la propriété?

En utilisant votre exemple:

@property (nonatomic, assign) NSArray *myArray; 
@synthesize myArray; 

... 

self.myArray = [[NSArray alloc] initWithObjects: @"foo", nil]; 
self.myArray = [[NSArray alloc] initWithObjects: @"bar", nil]; // MEMORY LEAK! 

Dans ce cas, vous devrez libérer manuellement votre Ivar en appelant release sur elle. Si vous ne le faites pas, vous aurez fui la mémoire.

Une autre chose intelligente à propos de l'avoir retenu (ou copié, moins bug sujettes) ce que vous pouvez dire:

self.myArray = nil; 

Cela libérera la variable et régler la référence à zéro, de sorte que vous éviter d'avoir vous-même dans le pétrin.

Je vois absolument votre point cependant. Il est beaucoup plus bavard d'écrire 3 lignes au lieu d'une. Vous pouvez comme @willcodejavaforfood suggère utiliser autorelease lorsque vous attribuez aux propriétés retenues , car il semble avoir manqué ). Mais Apple suggère que sur l'iPhone vous faites aussi peu autoreleasing que vous le pouvez, et nous écoutons toujours Apple comme de bons petits enfants.

Mise à jour:

Lorsque vous spécifiez une propriété comme (nonatomic, assignez) une synthétisent le code setter qui est généré ressemble à quelque chose comme ceci:

- (void)setMyArray:(NSArray *)newValue { 
    myArray = newValue; 
} 

Si vous le d'autre part le définir comme (nonatomic, conserver) vous obtenez:

- (void)setMyArray:(NSArray *)newValue { 
    if (myArray != newValue) { 
     [myArray release]; 
     myArray = [newValue retain]; 
    } 
} 

J'espère que cela éclaircira les choses.

+0

Alors quelle est la différence, si j'avais fait comme Apple suggéré avec conserver etc, puis plus tard réalloué la variable comme suit NSArray * aSecondArray = [[NSArray alloc] initWithObjects: @ "bar", nil]; self.myArray = aSecondArray; [aSecondArray release]; Je n'appelle pas non plus la publication ici sur self.myArray, alors pourquoi n'est-ce pas une fuite de mémoire? –

+0

Parce que lorsque vous déclarez une propriété en tant que 'retain', le code de la propriété générée appellera la libération sur l'instance précédente (si elle n'est pas nulle) avant d'affecter la nouvelle valeur. –

+0

Oui, mais je ne le déclare pas comme retenir. Je le déclare en tant qu'attribuer ... –

1

Vous pouvez.

Je veux dire, c'est ce que je fais dans mon programme parce que je n'aime pas utiliser la propriété de retenir ^^

Il ne fonctionne pas? Quelle est l'erreur?

Par la façon dont vous pouvez simplement écrire

myArray = [[NSArray alloc] initWithObjects etc etc...]; 
5

L'un des points de propriétés est de nous soulager d'avoir à penser à nous-mêmes la gestion de la mémoire. Faire la propriété assign et ensuite assigner un objet retenu dans celui-ci défait le but de l'utilisation de la propriété.

Il est très simple à faire:

@property (nonatomic, retain) NSArray * myArray; 
@synthesize myArray; 

self.myArray = [NSArray arrayWithObjects:etc, etc1, etc2, nil]; 

Et puis toute la gestion de la mémoire est pris en charge pour vous.

+0

True, mais vous utilisez une méthode d'usine renvoyant une instance autoeleased. Apple suggère que nous utilisons le moins possible les variables auto-libérées. Certes, c'est surtout un problème dans les situations où vous avez de grandes quantités d'instances autoreleased comme lors de l'analyse d'un document XML, mais c'est une bonne règle empirique. Vous pouvez bien sûr aussi utiliser le pool autorelease si cela devient un problème, mais dans la plupart des cas, c'est excessif. –

+0

Non non, je voulais dire au lieu d'écrire @property (non atomique, conserver) écrire @property (nonatomic, assigner) –

+1

@Thomas argument de hareng rouge, puisque l'objet sera conservé par la propriété de toute façon, ce qui signifie qu'il est auto-libéré doesn ' t importe. Il continuera à exister. –

1

Vous pouvez écrire:

self.myArray = [[[NSArray alloc] initWithObjects etc etc...] autorelease]; 

(noter l'ajout du autorelease)

Bien qu'il serait plus simple d'écrire:

self.myArray = [NSArray arrayWithObjects etc etc...]; 

puristes considèrent que les vous ne devriez pas Mettez les choses dans un pool d'autorelease à moins que vous n'en ayez vraiment besoin, cependant, si cela rend votre code plus simple, dites-le, les frais généraux de performance sont négligeables dans la plupart des cas.

Si vous utilisez plutôt une propriété assign, vous devez vous assurer que vous libérez vous-même l'ancien contenu de myArray, ce qui annule une grande partie de l'avantage et de la simplicité.

1

La réponse courte est que l'utilisation de assign entraînera probablement des fuites de mémoire. Sauf si vous êtes très prudent.

En déclarant la propriété de tableau comme retain, vous indiquez que l'objet doit prendre possession du tableau en lui envoyant un message retain et, plus important encore, qu'il doit envoyer un release message lorsqu'il ne souhaite plus garder le tableau autour. Lorsque vous utilisez assign, l'objet n'envoie aucun message retain ou release au tableau. Donc, dans l'exemple que vous donnez, il n'y a pas de problème ENCORE. Vous avez créé un tableau avec un nombre de retenues de un (conceptuellement) et donné à votre objet. Dans ce cas, le tableau se bloque en mémoire avec un nombre de retenues de 1 exactement comme si vous utilisiez l'attribut retain lors de la déclaration de la propriété. Le problème vient quand vous voulez changer la valeur de myArray. Si votre propriété est déclarée avec retain, une mission fera quelque chose comme ceci:

- (void)setMyArray:(NSArray *)newArray { 
    if (myArray != newArray) { 
     [myArray release]; // Old value gets released 
     myArray = [newValue retain]; 
    } 
} 

L'ancien myArray est envoyé un message release indiquant que l'objet est fait avec elle. Si le nombre de retenue de myArray tombe à zéro, il sera désalloué et sa mémoire sera récupérée. Si la propriété est déclarée avec assign, cela se produit essentiellement:

- (void)setMyArray:(NSArray *)newArray { 
    myArray = newArray; 
} 

L'objet oublie le tableau à myArray sans l'envoyer un message release. Par conséquent, le tableau précédemment référencé par myArray ne sera probablement pas désalloué. Par conséquent, ce n'est pas l'affectation qui pose problème. C'est l'échec de libérer la matrice pendant la réaffectation qui causera la fuite de mémoire. Cela peut ne pas poser de problème si un autre objet possède le tableau.

Si un autre objet est propriétaire du tableau, et le tableau vient d'être référencé par myArray, que autre objet est en charge de se assurer que le tableau reste aussi longtemps que myArray a besoin et de libérer le tableau quand il n'y a pas plus longtemps nécessaire. C'est le modèle généralement utilisé pour les délégués. Vous devez ensuite faire attention à ne pas accéder à myArray après que cet autre objet a libéré le tableau auquel il fait référence. Essentiellement, cela revient à savoir à qui appartient le tableau référencé par myArray. Si un autre objet le possède et le gère en le conservant et en le relâchant au besoin, il est parfaitement acceptable que votre objet le fasse simplement référence. Toutefois, si votre objet est le propriétaire de myArray (et le publiera dans dealloc), il est plus logique d'utiliser l'attribut retain. Sinon, afin d'éviter les fuites, vous aurez besoin d'autres objets pour libérer le contenu de myArray avant d'appeler le setter de votre objet, puisque votre setter assign ne le fera pas pour vous.

+0

Salut James, sûrement quand j'utilise self.myArray les setters et les getters d'Apple s'occuperont de tout ça. Je suis d'accord que si j'avais dit simplement myArray = [[NSArray alloc] init etc ..] ALORS j'aurais dû gérer moi-même les versions ... –

+0

En fait, les techniques de gestion de la mémoire utilisées par les setters synthétisés et les getters * dépendent * les attributs de gestion de la mémoire ('assign',' retain', 'copy') que vous spécifiez dans votre déclaration' @ property'. C'est précisément ce qu'ils sont là pour. C'est la façon dont vous dites au compilateur comment vous voulez que vos setters et getters gèrent la mémoire. Si vous spécifiez 'assign', aucune gestion de la mémoire n'est faite pour vous. C'est génial pour les ints et les floats, mais généralement pas ce que vous voulez pour les objets. Si vous spécifiez 'retain',' retain' sera envoyé aux objets assignés et 'release' sera envoyé aux valeurs précédentes. –

+0

Oh je vois. Eh bien, je ne le savais pas, merci d'avoir signalé cela. Donc, fondamentalement, quand une propriété est déclarée en utilisant (assign), il n'y a absolument AUCUNE différence (aussi loin que les setters et les getters vont) entre self.myArray = .. et myArray = ..? –

1

La gestion de la mémoire dans Cocoa (et Cocoa Touch) est très fortement basée sur conventions. Une de ces conventions est que les objets prennent possession d'autres objets qu'ils doivent garder, ce qui signifie qu'ils doivent conserver (revendiquer la propriété) et libérer (abandonner la propriété) ces objets. Si vous en définissez une propriété assign et que chaque appelant doit gérer la mémoire pour vous, cela viole les conventions de gestion de la mémoire.

C'est aussi une mauvaise conception de programme, parce que plutôt que d'avoir un endroit (le setter) qui s'occupe de la gestion de cette propriété, vous étendez la responsabilité à chaque endroit qui accède à la propriété. La séparation claire des préoccupations est l'un des aspects les plus importants de la conception. En bref: Vous pouvez faire comme vous le demandez. C'est juste pire à tous égards. Cela va à l'encontre des hypothèses de Cocoa, cela rend les bugs plus probables, complique votre conception et bloque votre code. Toutefois, dans les cas où vous définissez les propriétés self, vous pouvez faire quelque chose comme ce que vous voulez. Au lieu d'écrire self.someProperty = [[NSString alloc] initWithString:@"Foo"], vous pouvez simplement écrire someProperty = [[NSString alloc] initWithString:@"Foo"] (en supposant que someProperty est la variable d'instance sous-jacente). C'est en fait la manière normale de le faire dans une méthode d'initialisation ou une méthode dealloc. Cela vous permet d'assigner simplement la variable dans l'implémentation interne de votre classe sans exiger de tous ceux qui utilisent la classe de faire la gestion de la mémoire de la classe pour cela.

+0

Salut Chuck. Comment faire la propriété assigner exige que chaque appelant gère la mémoire pour moi? Il est encore accessible via un setter et getter généré par Apple. –

+0

@Karl Griffiths: Une propriété 'assign' * par définition * ne fait aucune sorte de gestion de la mémoire. Il assigne simplement la valeur à la variable d'instance, aucune retenue ou libération impliquée - c'est ce que signifie 'affecter'. Le setter '@ synthesize' créé pour une propriété' assign' suit ce contrat. Donc si 'someProperty' est déclaré' assign' et que vous écrivez 'object.someProperty = [[NSString alloc] initWithString: @" Foo "]', alors la valeur utilisée dans 'someProperty' est divulguée. L'appelant devrait d'abord libérer l'ancienne valeur. Ainsi, l'appelant doit gérer la mémoire de l'objet pour cela. – Chuck

+0

Excellent! :-) Maintenant, je comprends mieux. Alors que se passerait-il si la propriété était déclarée assign, et alors vous avez dit ceci ... self.myArray = nil ;? Serait-ce aussi une fuite de mémoire? –

Questions connexes