web-dev-qa-db-fra.com

Déclaration / définition des emplacements de variables dans l’objectif C?

Depuis que j'ai commencé à travailler sur les applications iOS et Objective C, je suis vraiment perplexe devant les différents emplacements où l'on peut déclarer et définir des variables. D'un côté, nous avons l'approche traditionnelle C, de l'autre, 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 d voulez-vous 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
  • D'après ce que je comprends de 1 et 4, il s'agit de déclarations et de définitions basées sur des fichiers de style C qui ne comprennent absolument pas le concept de classe et qui doivent donc être utilisées exactement de la manière dont elles seraient utilisées en C. Je les ai vues utilisé pour implémenter des singletons statiques à base de variables auparavant. Y a-t-il d'autres utilisations pratiques qui me manquent?
  • En travaillant avec iOS, j’ai le sentiment que les ivars ont pratiquement été supprimés en dehors de la directive @synthesize et qu’ils peuvent donc être pratiquement ignorés. Est-ce le cas?
  • Concernant 5: pourquoi voudrais-je jamais déclarer des méthodes dans des interfaces privées? Mes méthodes de classes privées semblent bien compiler sans déclaration dans l'interface. Est-ce surtout pour la lisibilité?

Merci beaucoup, les gars!

109
Alexandr Kurilin

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

Avant Objective-C "moderne" (dans le "vieux" Obj-C 2.0), vous n'aviez pas beaucoup de choix. Auparavant, les variables d’instance étaient déclarées dans l’entête entre les accolades { }:

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

Vous n'avez pu accéder à ces variables que dans votre implémentation, mais pas à partir d'autres classes. Pour ce faire, vous deviez déclarer des méthodes d'accès, 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 pouviez obtenir et définir cette variable d'instance à partir d'autres classes également, en utilisant la syntaxe habituelle du crochet 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 mettre en œuvre manuellement chaque méthode d'accès était assez ennuyant, @property et @synthesize ont été introduits pour générer automatiquement les méthodes d'accès:

// 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 pourrez toujours utiliser la syntaxe entre crochets comme auparavant. Mais vous pouvez également utiliser la syntaxe à points 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 ignorer @synthesize aussi. Si vous ne déclarez pas un ivar, le compilateur l'ajoutera pour vous et générera également les méthodes d'accès sans que vous ayez à utiliser @synthesize.

Le nom par défaut pour l'ivar généré automatiquement est le nom ou 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 des ivars dans l'en-tête. Mais parce que la seule raison pour laquelle vous voulez faire cela (et non déclarer une propriété) est de créer une variable privée, vous pouvez maintenant le faire aussi dans le fichier d'implémentation et c'est la méthode préférée.

Un @interface Le bloc dans le fichier d'implémentation est en fait un 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 propriété readonly dans votre en-tête.

@property (nonatomic, readonly) myReadOnlyVar;

et redéclarez-le dans votre fichier d'implémentation en tant que readwrite pour pouvoir le définir à l'aide de la syntaxe de la propriété et pas uniquement via un accès direct à ivar.

Quant à la déclaration de variables complètement en dehors de toute @interface ou @implementation block, oui, ce sont des variables en C simples et fonctionnent exactement de la même manière.

147
DrummerB

Tout d’abord, lisez la réponse de DrummerB. C'est un bon aperçu du pourquoi et de ce que vous devriez 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 ne va 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
  • enums
  • externes

Les externes ressemblent à des déclarations de variables, mais ils ne sont que la promesse de les déclarer ailleurs. Dans ObjC, elles ne devraient être utilisées que pour déclarer des constantes, et généralement uniquement des constantes de chaîne. Par exemple:

extern NSString * const MYSomethingHappenedNotification;

Dans votre fichier .m, Vous déclareriez alors la constante actuelle:

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 particulièrement les objets C++ s'il s'agit d'une classe ObjC++), ou les ivars qui ont une sémantique de stockage étrange (comme des 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 @ synthétiser. Clang (Xcode) le fera pour vous, et vous devriez le laisser.

Au cours des 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-synthèse). Donc, quand vous voyez l'ancien code, cela peut être un peu déroutant. C’est la confusion qui découle de la simplicité: D

41
Rob Napier

Je suis aussi assez nouvelle, alors j'espère ne rien foirer.

1 & 4: Variables globales de style C: elles ont une portée de fichier. La différence entre les deux réside dans le fait que, puisqu'ils couvrent l'ensemble du fichier, le premier sera disponible pour tous ceux qui importent 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 à l'aide de propriétés, ce qui rend la gestion de la mémoire simple et agréable, ainsi que la notation par points facile à comprendre.

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

3 & 7: Déclarations de méthodes publiques et de propriétés, 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 boîte noire. S'ils n'ont pas besoin de le savoir, mettez-le là. Je le fais aussi par souci de lisibilité, je ne sais pas s'il y a d'autres raisons.

6
Metabble

Ceci est un exemple de toutes sortes de variables déclarées dans 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 d'aucune autre classe. Ceci est un problème de visibilité, donc les déclarer avec @property ou @public ne le change pas.

Dans un constructeur, il est recommandé d’accéder aux variables déclarées avec @property utilisant le trait de soulignement à la place self pour é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 à l'aide du moteur d'exécution.

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)];
    }
}

Cela imprime

iMadeVisible 
iMadeVisible2 
iMadeVisible3

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

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

Je n'ai pas abordé les attributs de gestion de la mémoire, ni les attributs readonly/readwrite, getter/setter.

5
Jano