Déclaration / définition des emplacements des variables dans ObjectiveC?

113

Depuis que j'ai commencé à travailler sur les applications iOS et l'objectif C, j'ai été vraiment intrigué par les différents endroits où l'on pouvait déclarer et définir des variables. D'une part, nous avons l'approche traditionnelle C, d'autre part, nous avons les nouvelles directives ObjectiveC qui ajoutent OO en plus de cela. Pourriez-vous m'aider à comprendre les meilleures pratiques et les situations dans lesquelles je voudrais utiliser ces emplacements pour mes variables et peut-être corriger ma compréhension actuelle?

Voici un exemple de classe (.h et .m):

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

et

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • Ma compréhension de 1 et 4 est que ce sont des déclarations et des définitions basées sur des fichiers de style C qui n'ont aucune compréhension du concept de classe, et doivent donc être utilisées exactement comme elles seraient utilisées en C. Je les ai vues utilisé auparavant pour implémenter des singletons statiques basés sur des variables. Y a-t-il d'autres utilisations pratiques qui me manquent?
  • Mon expérience avec iOS est que les ivars ont été presque complètement supprimés en dehors de la directive @synthesize et peuvent donc être ignorés. Est-ce le cas?
  • Concernant 5: pourquoi voudrais-je déclarer des méthodes dans des interfaces privées? Mes méthodes de classe privée semblent bien compiler sans déclaration dans l'interface. Est-ce principalement pour la lisibilité?

Merci beaucoup, les amis!

Alexandr Kurilin
la source

Réponses:

154

Je peux comprendre votre confusion. D'autant plus que les récentes mises à jour de Xcode et du nouveau compilateur LLVM ont changé la façon dont les ivars et les propriétés peuvent être déclarés.

Avant l'Obj-C "moderne" (dans "l'ancien" Obj-C 2.0), vous n'aviez pas beaucoup de choix. Les variables d'instance étaient déclarées dans l'en-tête entre les accolades { }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

Vous avez pu accéder à ces variables uniquement dans votre implémentation, mais pas à partir d'autres classes. Pour ce faire, vous deviez déclarer des méthodes d'accesseur, qui ressemblent à ceci:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

De cette façon, vous avez également pu obtenir et définir cette variable d'instance à partir d'autres classes, en utilisant la syntaxe habituelle entre crochets pour envoyer des messages (méthodes d'appel):

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

Parce que déclarer et implémenter manuellement chaque méthode d'accesseur était assez ennuyeux @propertyet a @synthesizeété introduit pour générer automatiquement les méthodes d'accesseur:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

Le résultat est un code beaucoup plus clair et plus court. Les méthodes d'accès seront implémentées pour vous et vous pouvez toujours utiliser la syntaxe entre crochets comme auparavant. Mais en plus, vous pouvez également utiliser la syntaxe dot pour accéder aux propriétés:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

Depuis Xcode 4.4, vous n'avez plus à déclarer vous-même une variable d'instance et vous pouvez @synthesizeégalement sauter . Si vous ne déclarez pas d'ivar, le compilateur l'ajoutera pour vous et générera également les méthodes d'accesseur sans que vous ayez à utiliser @synthesize.

Le nom par défaut de l'ivar généré automatiquement est le nom de votre propriété commençant par un trait de soulignement. Vous pouvez changer le nom de l'ivar généré en utilisant@synthesize myVar = iVarName;

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

Cela fonctionnera exactement comme le code ci-dessus. Pour des raisons de compatibilité, vous pouvez toujours déclarer ivars dans l'en-tête. Mais comme la seule raison pour laquelle vous voudriez faire cela (et non déclarer une propriété) est de créer une variable privée, vous pouvez maintenant le faire également dans le fichier d'implémentation et c'est la méthode préférée.

Un @interfacebloc dans le fichier d'implémentation est en fait une extension et peut être utilisé pour transmettre des méthodes de déclaration (qui ne sont plus nécessaires) et pour (re) déclarer des propriétés. Vous pouvez par exemple déclarer une readonlypropriété dans votre en-tête.

@property (nonatomic, readonly) myReadOnlyVar;

et redéclarez-le dans votre fichier d'implémentation readwritepour pouvoir le définir en utilisant la syntaxe de propriété et pas seulement via un accès direct à ivar.

En ce qui concerne la déclaration de variables complètement en dehors de tout @interfaceou @implementationbloc, oui, ce sont des variables C simples et fonctionnent exactement de la même manière.

BatteurB
la source
2
très bonne réponse! A noter également: stackoverflow.com/questions/9859719/…
nycynik
44

Tout d'abord, lisez la réponse de @ DrummerB. C'est un bon aperçu des pourquoi et de ce que vous devez généralement faire. Dans cet esprit, à vos questions spécifiques:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

Aucune définition de variable réelle n'est disponible ici (il est techniquement légal de le faire si vous savez exactement ce que vous faites, mais ne le faites jamais). Vous pouvez définir plusieurs autres types de choses:

  • typdefs
  • énumérations
  • externes

Les externes ressemblent à des déclarations de variables, mais ils ne sont qu'une promesse de le déclarer ailleurs. Dans ObjC, ils ne doivent être utilisés que pour déclarer des constantes, et généralement uniquement des constantes de type chaîne. Par exemple:

extern NSString * const MYSomethingHappenedNotification;

Vous .mdéclareriez alors dans votre fichier la constante réelle:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

Comme l'a noté DrummerB, c'est un héritage. Ne mettez rien ici.


// 3) class-specific method / property declarations

@end

Oui.


#import "SampleClass.h"

// 4) what goes here?

Constantes externes, comme décrit ci-dessus. Les variables statiques de fichier peuvent également aller ici. Ce sont l'équivalent des variables de classe dans d'autres langues.


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

Oui


@implementation SampleClass
{
    // 6) define ivars
}

Mais très rarement. Presque toujours, vous devriez autoriser clang (Xcode) à créer les variables pour vous. Les exceptions concernent généralement les ivars non-ObjC (comme les objets Core Foundation, et en particulier les objets C ++ s'il s'agit d'une classe ObjC ++), ou les ivars qui ont une sémantique de stockage étrange (comme les ivars qui ne correspondent pas à une propriété pour une raison quelconque).


// 7) define methods and synthesize properties from both public and private
//    interfaces

Généralement, vous ne devriez plus @synthesize. Clang (Xcode) le fera pour vous, et vous devriez le laisser.

Ces dernières années, les choses se sont considérablement simplifiées. L'effet secondaire est qu'il existe maintenant trois époques différentes (ABI fragile, ABI non fragile, ABI non fragile + auto-syntheisze). Donc, quand vous voyez l'ancien code, cela peut être un peu déroutant. Ainsi confusion découlant de la simplicité: D

Rob Napier
la source
Je me demande juste, mais pourquoi ne pas synthétiser explicitement? Je le fais parce que je trouve mon code plus facile à comprendre, surtout lorsque certaines propriétés ont synthétisé des accesseurs et certaines ont des implémentations personnalisées, car j'ai l'habitude de synthétiser. Y a-t-il des inconvénients à la synthèse explicite?
Metabble
Le problème avec son utilisation comme documentation est qu'il ne documente vraiment rien. Malgré l'utilisation de synthesize, vous avez peut-être remplacé un ou les deux accesseurs. Il n'y a aucun moyen de dire à partir de la ligne de synthèse quelque chose de vraiment utile. La seule chose pire que l'absence de documentation est une documentation trompeuse. Laisser de côté.
Rob Napier
3
Pourquoi le n ° 6 est-il rare? N'est-ce pas le moyen le plus simple d'obtenir une variable privée?
pfrank
Le moyen le plus simple et le meilleur d'obtenir une propriété privée est le n ° 5.
Rob Napier
1
@RobNapier Il est encore nécessaire d'utiliser @ synthesize parfois (par exemple si une propriété est en lecture seule a son accesseur remplacé)
Andy
6

Je suis aussi assez nouveau, alors j'espère que je ne gâche rien.

1 & 4: Variables globales de style C: elles ont une portée étendue au fichier. La différence entre les deux est que, puisqu'ils sont à l'échelle du fichier, le premier sera disponible pour quiconque importe l'en-tête tandis que le second ne l'est pas.

2: variables d'instance. La plupart des variables d'instance sont synthétisées et récupérées / définies via des accesseurs en utilisant des propriétés car cela rend la gestion de la mémoire agréable et simple, ainsi que vous donne une notation par points facile à comprendre.

6: Les ivars de mise en œuvre sont quelque peu nouveaux. C'est un bon endroit pour mettre des ivars privés, car vous ne voulez exposer que ce qui est nécessaire dans l'en-tête public, mais les sous-classes ne les héritent pas d'AFAIK.

3 & 7: Déclarations de méthodes et de propriétés publiques, puis implémentations.

5: interface privée. J'utilise toujours des interfaces privées chaque fois que je peux pour garder les choses propres et créer une sorte d'effet de boîte noire. S'ils n'ont pas besoin de le savoir, mettez-le là. Je le fais aussi pour la lisibilité, je ne sais pas s'il y a d'autres raisons.

Metabble
la source
1
Ne pensez pas que vous avez raté quoi que ce soit :) Quelques commentaires - # 1 & # 4 esp avec # 4 souvent vous voyez des variables de stockage statiques. # 1 vous verrez souvent le stockage externe spécifié, puis le stockage réel alloué dans # 4. # 2) uniquement si une sous-classe en a besoin pour une raison quelconque. # 5 n'est plus nécessaire de transmettre les méthodes de déclaration privées.
Carl Veazey
Ouais, je viens de vérifier moi-même la déclaration. Il donnait un avertissement si une méthode privée en appelait une autre qui était définie après elle sans déclaration avant, non? J'ai été un peu surpris quand ça ne m'a pas prévenu.
Metabble
Ouais, c'est une nouvelle partie du compilateur. Ils ont vraiment fait beaucoup de progrès ces derniers temps.
Carl Veazey
6

Ceci est un exemple de toutes sortes de variables déclarées en Objective-C. Le nom de la variable indique son accès.

Fichier: Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

Fichier: Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

Notez que les variables iNotVisible ne sont visibles depuis aucune autre classe. C'est un problème de visibilité, donc les déclarer avec @propertyou @publicne le change pas.

À l'intérieur d'un constructeur, il est recommandé d'accéder aux variables déclarées avec un @propertytrait de soulignement selfpour éviter les effets secondaires.

Essayons d'accéder aux variables.

Fichier: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

Fichier: Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

Nous pouvons toujours accéder aux variables non visibles en utilisant le runtime.

Fichier: Cow.m (partie 2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

Essayons d'accéder aux variables non visibles.

Fichier: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

Cette imprime

iMadeVisible 
iMadeVisible2 
iMadeVisible3

Notez que j'ai pu accéder au support ivar _iNotVisible2qui est privé pour la sous-classe. En Objective-C, toutes les variables peuvent être lues ou définies, même celles qui sont marquées @private, sans exception.

Je n'ai pas inclus les objets associés ou les variables C car ce sont des oiseaux différents. Comme pour les variables C, toute variable définie en dehors @interface X{}ou @implementation X{}est une variable C avec une portée de fichier et un stockage statique.

Je n'ai pas discuté des attributs de gestion de la mémoire, ni des attributs de lecture seule / readwrite, getter / setter.

Jano
la source