Comment gérer les protocoles Objective-C contenant des propriétés?

131

J'ai vu l'utilisation des protocoles Objective-C s'utiliser d'une manière telle que la suivante:

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

J'ai vu ce format utilisé au lieu d'écrire une superclasse concrète que les sous-classes étendent. La question est, si vous vous conformez à ce protocole, avez-vous besoin de synthétiser vous-même les propriétés? Si vous étendez une superclasse, la réponse est évidemment non, ce n'est pas nécessaire. Mais comment gérer les propriétés auxquelles un protocole doit se conformer?

À ma connaissance, vous devez toujours déclarer les variables d'instance dans le fichier d'en-tête d'un objet conforme à un protocole qui nécessite ces propriétés. Dans ce cas, pouvons-nous supposer qu'il ne s'agit que d'un principe directeur? Clairement, ce n'est pas le cas pour une méthode obligatoire. Le compilateur vous giflera pour avoir exclu une méthode requise répertoriée par un protocole. Quelle est l'histoire des propriétés?

Voici un exemple qui génère une erreur de compilation (Remarque: j'ai réduit le code qui ne reflète pas le problème en question):

MyProtocol.h

@protocol MyProtocol <NSObject>

@required
@property (nonatomic, retain) id anObject;

@optional

TestProtocolsViewController.h

- (void)iDoCoolStuff;

@end

#import <MyProtocol.h>

@interface TestProtocolsViewController : UIViewController <MyProtocol> {

}

@end

TestProtocolsViewController.m

#import "TestProtocolsViewController.h"

@implementation TestProtocolsViewController
@synthesize anObject; // anObject doesn't exist, even though we conform to MyProtocol.

- (void)dealloc {
    [anObject release]; //anObject doesn't exist, even though we conform to MyProtocol.
    [super dealloc];
}

@end     
Coocoo4Cocoa
la source

Réponses:

135

Le protocole dit simplement à tous ceux qui connaissent votre classe à travers le protocole, que la propriété anObjectsera là. Les protocoles ne sont pas réels, ils n'ont pas de variables ou de méthodes en eux-mêmes - ils décrivent uniquement un ensemble spécifique d'attributs qui est vrai pour votre classe afin que les objets qui y font référence puissent les utiliser de manière spécifique.

Cela signifie que dans votre classe qui est conforme à votre protocole, vous devez tout faire pour vous assurer qu'un objet fonctionne.

@propertyet @synthesizesont au cœur de deux mécanismes qui génèrent du code pour vous. @propertysignifie simplement qu'il y aura une méthode getter (et / ou setter) pour ce nom de propriété. Ces jours-ci à @propertyeux seuls, il suffit d'avoir également des méthodes et une variable de stockage créées pour vous par le système (vous deviez ajouter @sythesize). Mais vous devez avoir quelque chose pour accéder et stocker la variable.

Kendall Helmstetter Gelner
la source
80
Pour les propriétés définies dans un protocole, vous avez toujours besoin d'un "@synthesize" même dans le runtime moderne, ou vous devez dupliquer le "@property" dans votre définition d'interface pour obtenir une synthèse automatique.
Jeffrey Harris
@JeffreyHarris Et la même chose dans Swift ??
Karan Alangat
@KaranAlangat - il n'existe pas de \ @synthesize dans Swift, mais tout comme ObjC, vous devez déclarer la propriété dans une classe qui prétend être conforme au protocole. Dans Swift, vous pouvez créer une catégorie qui définit une implémentation par défaut d'une fonction, mais pour autant que j'ai pu le dire, vous ne pouvez pas avoir de propriété par défaut pour un protocole.
Kendall Helmstetter Gelner
31

Voici un exemple du mien qui fonctionne parfaitement, la définition du protocole tout d'abord:

@class ExampleClass;

@protocol ExampleProtocol

@required

// Properties
@property (nonatomic, retain) ExampleClass *item;

@end

Voici un exemple fonctionnel d'une classe prenant en charge ce protocole:

#import <UIKit/UIKit.h>
#import "Protocols.h"

@class ExampleClass;

@interface MyObject : NSObject <ExampleProtocol> {

    // Property backing store
    ExampleClass        *item;

}


@implementation MyObject

// Synthesize properties
@synthesize item;

@end
Reddersky
la source
14

tout ce que vous avez à faire est de laisser tomber un

@synthesize title;

dans votre implémentation et vous devriez être prêt. cela fonctionne de la même manière que simplement mettre la propriété dans votre interface de classe.

Éditer:

Vous voudrez peut-être le faire plus spécifiquement:

@synthesize title = _title;

Cela correspondra à la façon dont la synthèse automatique de xcode crée des propriétés et des ivars si vous utilisez l'auto-synthèse, de sorte que si votre classe a des propriétés d'un protocole et d'une classe, certains de vos ivars n'auront pas le format différent qui pourrait avoir un impact lisibilité.

Kevlar
la source
1
Etes-vous complètement sûr? J'ai une propriété facultative définie dans un protocole, et lorsque je ne la synthétise que dans une classe concrète conforme à ce protocole, j'obtiens une erreur de compilation affirmant que c'est une variable non déclarée. Aucune faute de frappe confirmée.
Coocoo4Cocoa
je ne suis pas sûr des propriétés optionnelles, mais une chose que j'ai oublié de mentionner comme mralex a dit est que vous devez la lier à une variable membre, soit en nommant cette variable title, soit en disant @synthesize title = myinstancevar;
Kevlar
2
Si vous utilisez le runtime moderne, @synthesize est tout ce dont vous avez besoin, les ivars sous-jacents seront créés pour vous. Si vous ciblez le x86 32 bits, vous obtiendrez l'erreur du compilateur mentionnée, car vous ciblez le runtime hérité.
Jeffrey Harris
1
La synthèse automatique a été introduite dans Xcode 4.4, mais selon un tweet de Graham Lee , elle ne couvre pas les propriétés déclarées dans les protocoles. Vous aurez donc toujours besoin de synthétiser manuellement ces propriétés.
cbowns
C'est un excellent point, je ne savais pas que l'ajout synthesizeétait suffisant. Cool!
Dan Rosenstark
9

Jetez un œil à mon article PROPRIÉTÉ EN PROTOCOLE

Supposons que j'ai MyProtocol qui déclare une propriété de nom et MyClass qui se conforme à ce protocole

Choses à noter

  1. La propriété identifier dans MyClass déclare et génère la variable getter, setter et backing _identifier
  2. La propriété name déclare uniquement que MyClass a un getter, setter dans l'en-tête. Il ne génère pas de getter, d'implémentation de setter et de variable de sauvegarde.
  3. Je ne peux pas redéclarer cette propriété de nom, car elle a déjà été déclarée par le protocole. Faites cela criera une erreur

    @interface MyClass () // Class extension
    
    @property (nonatomic, strong) NSString *name;
    
    @end

Comment utiliser la propriété dans le protocole

Donc, pour utiliser MyClass avec cette propriété de nom, nous devons faire soit

  1. Déclarez à nouveau la propriété (AppDelegate.h fait de cette façon)

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
  2. Se synthétiser

    @implementation MyClass
    
    @synthesize name;
    
    @end
onmyway133
la source
Les blocs de code imbriqués dans les listes doivent être mis en retrait de huit espaces par ligne. C'est une bizarrerie relativement inconnue de la syntaxe Markdown. J'ai édité votre réponse pour vous.
BoltClock
1

Architecture de protocole

Exemple: 2 classes (Person et Serial) veulent utiliser le service de Viewer ... et doivent se conformer à ViewerProtocol. viewerTypeOfDescription est une propriété obligatoire que les classes d'abonné doivent se conformer.

typedef enum ViewerTypeOfDescription {
    ViewerDataType_NSString,
    ViewerDataType_NSNumber,
} ViewerTypeOfDescription;

@protocol ViewerProtocol
@property ViewerTypeOfDescription viewerTypeOfDescription;
- (id)initConforming;
- (NSString*)nameOfClass;
- (id)dataRepresentation;
@end

@interface Viewer : NSObject
+ (void) printLargeDescription:(id <ViewerProtocol>)object;
@end

@implementation Viewer
+ (void) printLargeDescription:(id <ViewerProtocol>)object {
    NSString *data;
    NSString *type;
    switch ([object viewerTypeOfDescription]) {
        case ViewerDataType_NSString: {
            data=[object dataRepresentation];
            type=@"String";
            break;
        }
        case ViewerDataType_NSNumber: {
            data=[(NSNumber*)[object dataRepresentation] stringValue];
            type=@"Number";
            break;
        }
        default: {
            data=@"";
            type=@"Undefined";
            break;
        }
    }
    printf("%s [%s(%s)]\n",[data cStringUsingEncoding:NSUTF8StringEncoding],
           [[object nameOfClass] cStringUsingEncoding:NSUTF8StringEncoding],
           [type cStringUsingEncoding:NSUTF8StringEncoding]);
}
@end


/* A Class Person */

@interface Person : NSObject <ViewerProtocol>
@property NSString *firstname;
@property NSString *lastname;
@end

@implementation Person
// >>
@synthesize viewerTypeOfDescription;
// <<
@synthesize firstname;
@synthesize lastname;
// >>
- (id)initConforming {
    if (self=[super init]) {
        viewerTypeOfDescription=ViewerDataType_NSString;
    }
    return self;
}
- (NSString*)nameOfClass {
    return [self className];
}
- (NSString*) dataRepresentation {
    if (firstname!=nil && lastname!=nil) {
        return [NSString stringWithFormat:@"%@ %@", firstname, lastname];
    } else if (firstname!=nil) {
        return [NSString stringWithFormat:@"%@", firstname];
    }
    return [NSString stringWithFormat:@"%@", lastname];
}
// <<
@end



/* A Class Serial */

@interface Serial : NSObject <ViewerProtocol>
@property NSInteger amount;
@property NSInteger factor;
@end

@implementation Serial
// >>
@synthesize viewerTypeOfDescription;
// <<
@synthesize amount;
@synthesize factor;
// >>
- (id)initConforming {
    if (self=[super init]) {
        amount=0; factor=0;
        viewerTypeOfDescription=ViewerDataType_NSNumber;
    }
    return self;
}
- (NSString*)nameOfClass {
    return [self className];
}
- (NSNumber*) dataRepresentation {
    if (factor==0) {
        return [NSNumber numberWithInteger:amount];
    } else if (amount==0) {
        return [NSNumber numberWithInteger:0];
    }
    return [NSNumber numberWithInteger:(factor*amount)];
}
// <<
@end




int main(int argc, const char * argv[])
{

    @autoreleasepool {

        Person *duncan=[[Person alloc]initConforming];
        duncan.firstname=@"Duncan";
        duncan.lastname=@"Smith";

        [Viewer printLargeDescription:duncan];

        Serial *x890tyu=[[Serial alloc]initConforming];
        x890tyu.amount=1564;

        [Viewer printLargeDescription:x890tyu];

        NSObject *anobject=[[NSObject alloc]init];

        //[Viewer printLargeDescription:anobject];
        //<< compilator claim an issue the object does not conform to protocol

    }
    return 0;
}

Un autre exemple avec l'héritage de protocole sur le sous-classement

typedef enum {
    LogerDataType_null,
    LogerDataType_int,
    LogerDataType_string,
} LogerDataType;

@protocol LogerProtocol
@property size_t numberOfDataItems;
@property LogerDataType dataType;
@property void** data;
@end

@interface Loger : NSObject
+ (void) print:(id<LogerProtocol>)object;
@end

@implementation Loger
+ (void) print:(id<LogerProtocol>)object {
    if ([object numberOfDataItems]==0) return;
    void **data=[object data];
    for (size_t i=0; i<[object numberOfDataItems]; i++) {
        switch ([object dataType]) {
            case LogerDataType_int: {
                printf("%d\n",(int)data[i]);
            break;
            }
            case LogerDataType_string: {
                printf("%s\n",(char*)data[i]);
                break;
            }
            default:
            break;
        }
    }
}
@end


// A Master Class

@interface ArrayOfItems : NSObject  <LogerProtocol>
@end

@implementation ArrayOfItems
@synthesize dataType;
@synthesize numberOfDataItems;
@synthesize data;
- (id)init {
    if (self=[super init]) {
        dataType=LogerDataType_null;
        numberOfDataItems=0;
    }
    return self;
}
@end

// A SubClass

@interface ArrayOfInts : ArrayOfItems
@end

@implementation ArrayOfInts
- (id)init {
    if (self=[super init]) {
        self.dataType=LogerDataType_int;
    }
    return self;
}
@end

// An other SubClass

@interface ArrayOfStrings : ArrayOfItems
@end

@implementation ArrayOfStrings
- (id)init {
    if (self=[super init]) {
        self.dataType=LogerDataType_string;
    }
    return self;
}
@end


int main(int argc, const char * argv[])
{

    @autoreleasepool {

        ArrayOfInts *arr=[[ArrayOfInts alloc]init];
        arr.data=(void*[]){(int*)14,(int*)25,(int*)74};
        arr.numberOfDataItems=3;

        [Loger print:arr];

        ArrayOfStrings *arrstr=[[ArrayOfStrings alloc]init];
        arrstr.data=(void*[]){(char*)"string1",(char*)"string2"};
        arrstr.numberOfDataItems=2;

        [Loger print:arrstr];

    }
    return 0;
}
Luc-Olivier
la source
0

La variable, anObject, doit être définie dans votre définition de classe TestProtocolsViewController, le protocole vous informe simplement qu'il devrait être là.

Les erreurs du compilateur vous disent la vérité - la variable n'existe pas. @properties ne sont que des aides après tout.

mralex
la source