Come gestire i protocolli Objective-C che contengono proprietà?


131

Ho visto l'utilizzo dei protocolli Objective-C utilizzati in modo simile al seguente:

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

Ho visto questo formato usato invece di scrivere una superclasse concreta che si estende alle sottoclassi. La domanda è: se sei conforme a questo protocollo, devi sintetizzare tu stesso le proprietà? Se stai estendendo una superclasse, la risposta è ovviamente no, non è necessario. Ma come si gestiscono le proprietà a cui un protocollo richiede la conformità?

Per quanto ne so, è ancora necessario dichiarare le variabili di istanza nel file di intestazione di un oggetto conforme a un protocollo che richiede queste proprietà. In tal caso, possiamo presumere che siano solo un principio guida? Chiaramente lo stesso non è il caso di un metodo richiesto. Il compilatore ti schiaffeggerà il polso per escludere un metodo richiesto elencato da un protocollo. Qual è la storia dietro le proprietà?

Ecco un esempio che genera un errore di compilazione (Nota: ho tagliato il codice che non riflette sul problema attuale):

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     

Risposte:


135

Il protocollo sta solo dicendo a tutti coloro che conoscono la tua classe attraverso il protocollo, che la proprietà anObjectsarà lì. I protocolli non sono reali, non hanno variabili o metodi stessi: descrivono solo un insieme specifico di attributi vero per la tua classe in modo che gli oggetti che contengono riferimenti ad essi possano usarli in modi specifici.

Ciò significa che nella tua classe conforme al tuo protocollo, devi fare di tutto per assicurarti che anObject funzioni.

@propertye @synthesizesono alla base due meccanismi che generano codice per te. @propertysta solo dicendo che ci sarà un metodo getter (e / o setter) per quel nome di proprietà. Al giorno d'oggi @propertyè sufficiente avere anche metodi e una variabile di memoria creata per te dal sistema (era necessario aggiungere @sythesize). Ma devi avere qualcosa per accedere e memorizzare la variabile.


80
Per le proprietà definite in un protocollo, è ancora necessario un "@synthesize" anche nel runtime moderno, oppure è necessario duplicare "@property" nella definizione dell'interfaccia per ottenere la sintesi automatica.
Jeffrey Harris,

@JeffreyHarris Che dire dello stesso in Swift ??
Karan Alangat,

@KaranAlangat - in Swift non esiste qualcosa come \ @synthesize, ma proprio come ObjC è necessario dichiarare la proprietà in una classe che afferma di essere conforme al protocollo. In Swift puoi creare una categoria che definisce un'implementazione predefinita di una funzione, ma per quanto ne so, non puoi avere una proprietà predefinita per un protocollo.
Kendall Helmstetter Gelner,

31

Ecco un mio esempio che funziona perfettamente, la definizione del protocollo prima di tutto:

@class ExampleClass;

@protocol ExampleProtocol

@required

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

@end

Di seguito è riportato un esempio funzionante di una classe che supporta questo protocollo:

#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

14

tutto quello che devi fare è rilasciare a

@synthesize title;

nella tua implementazione e dovresti essere pronto. funziona allo stesso modo di mettere la proprietà nell'interfaccia della tua classe.

Modificare:

Potresti voler fare questo in modo più specifico:

@synthesize title = _title;

Ciò andrà in linea con il modo in cui la sintesi automatica di xcode crea proprietà e ivar se usi l'auto-sintesi, quindi in questo modo se la tua classe ha proprietà da un protocollo e una classe, alcuni dei tuoi ivar non avranno il diverso formato che potrebbe influire leggibilità.


1
Ne sei completamente sicuro? Ho una proprietà opzionale impostata in un protocollo e quando @synthesize solo in una classe concreta conforme a quel protocollo, viene visualizzato un errore del compilatore che afferma che è una variabile non dichiarata. Nessun errore di battitura confermato.
Coocoo4Cocoa,

non sono sicuro delle proprietà opzionali, ma una cosa che ho dimenticato di menzionare come ha detto mralex è che devi legarlo a una variabile membro, sia nominando quel titolo variabile, sia dicendo @synthesize title = myinstancevar;
Kevlar,

2
Se stai utilizzando il tempo di esecuzione moderno, @synthesize è tutto ciò di cui hai bisogno, gli ivar sottostanti saranno creati per te. Se scegli come target x86 a 32 bit, visualizzerai l'errore del compilatore menzionato, perché hai come target il runtime legacy.
Jeffrey Harris,

1
La sintesi automatica è stata introdotta in Xcode 4.4, ma secondo un tweet di Graham Lee , non copre le proprietà dichiarate nei protocolli. Quindi dovrai comunque sintetizzare manualmente quelle proprietà.
Arriva il

Questo è un ottimo punto, non ho capito che l'aggiunta di synthesizeera sufficiente. Freddo!
Dan Rosenstark,

9

Dai un'occhiata al mio articolo PROPRIETÀ IN PROTOCOLLO

Supponiamo di avere MyProtocol che dichiara una proprietà name e MyClass conforme a questo protocollo

Cose degne di nota

  1. La proprietà identificatore in MyClass dichiara e genera la variabile _identifier getter, setter e backing
  2. La proprietà name dichiara solo che MyClass ha un getter, setter nell'intestazione. Non genera getter, implementazione del setter e variabili di supporto.
  3. Non riesco a dichiarare nuovamente questa proprietà del nome, come già dichiarato dal protocollo. Fare questo urlerà un errore

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

Come utilizzare la proprietà nel protocollo

Quindi, per usare MyClass con quella proprietà name, dobbiamo fare entrambe le cose

  1. Dichiarare di nuovo la proprietà (AppDelegate.h fa in questo modo)

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
  2. Sintetizziamo noi stessi

    @implementation MyClass
    
    @synthesize name;
    
    @end

I blocchi di codice nidificati all'interno degli elenchi devono essere rientrati di otto spazi per riga. È una stranezza relativamente sconosciuta della sintassi di Markdown. Ho modificato la tua risposta per te.
BoltClock

1

Architettura del protocollo

Esempio: 2 classi (Person e Serial) vogliono utilizzare il servizio di Viewer ... e devono essere conformi a ViewerProtocol. viewerTypeOfDescription è una classe obbligatoria per cui gli abbonati devono conformarsi.

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 altro esempio con ereditarietà di protocollo su sottoclasse

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

0

La variabile, anObject, deve essere definita nella definizione della classe TestProtocolsViewController, il protocollo ti sta semplicemente informando che dovrebbe essere lì.

Gli errori del compilatore ti dicono la verità - la variabile non esiste. @properties sono solo aiutanti dopo tutto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.