Wie gehe ich mit Objective-C-Protokollen um, die Eigenschaften enthalten?

131

Ich habe gesehen, dass die Verwendung von Objective-C-Protokollen auf folgende Weise verwendet wird:

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

Ich habe gesehen, dass dieses Format verwendet wurde, anstatt eine konkrete Oberklasse zu schreiben, die Unterklassen erweitert. Die Frage ist, ob Sie die Eigenschaften selbst synthetisieren müssen, wenn Sie dieses Protokoll einhalten. Wenn Sie eine Oberklasse erweitern, lautet die Antwort offensichtlich nein, das müssen Sie nicht. Aber wie geht man mit Eigenschaften um, denen ein Protokoll entsprechen muss?

Nach meinem Verständnis müssen Sie die Instanzvariablen in der Header-Datei eines Objekts deklarieren, das einem Protokoll entspricht, das diese Eigenschaften erfordert. Können wir in diesem Fall annehmen, dass sie nur ein Leitprinzip sind? Offensichtlich ist dies bei einer erforderlichen Methode nicht der Fall. Der Compiler schlägt Ihnen auf das Handgelenk, um eine erforderliche Methode auszuschließen, die in einem Protokoll aufgeführt ist. Was ist die Geschichte hinter Immobilien?

Hier ist ein Beispiel, das einen Kompilierungsfehler generiert (Hinweis: Ich habe den Code gekürzt, der das vorliegende Problem nicht berücksichtigt):

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
quelle

Antworten:

135

Das Protokoll sagt nur jedem, der über das Protokoll über Ihre Klasse Bescheid weiß, dass die Eigenschaft anObjectdort sein wird. Protokolle sind nicht real, sie haben selbst keine Variablen oder Methoden - sie beschreiben nur einen bestimmten Satz von Attributen, die für Ihre Klasse zutreffen, damit Objekte, die Verweise auf sie enthalten, sie auf bestimmte Weise verwenden können.

Das bedeutet, dass Sie in Ihrer Klasse, die Ihrem Protokoll entspricht, alles tun müssen, um sicherzustellen, dass ein Objekt funktioniert.

@propertyund @synthesizesind im Kern zwei Mechanismen, die Code für Sie generieren. @propertysagt nur, dass es eine Getter- (und / oder Setter-) Methode für diesen Eigenschaftsnamen geben wird. Diese Tage @propertyallein reichen aus, um auch Methoden und eine Speichervariable vom System für Sie erstellen zu lassen (die Sie früher hinzufügen mussten @sythesize). Sie müssen jedoch über etwas verfügen, um auf die Variable zugreifen und sie speichern zu können.

Kendall Helmstetter Gelner
quelle
80
Für Eigenschaften, die in einem Protokoll definiert sind, benötigen Sie auch in der modernen Laufzeit noch ein "@synthesize", oder Sie müssen das "@property" in Ihrer Schnittstellendefinition duplizieren, um die automatische Synthese zu erhalten.
Jeffrey Harris
@ JeffreyHarris Was ist mit dem gleichen in Swift?
Karan Alangat
@KaranAlangat - In Swift gibt es kein \ @synthesize, aber genau wie bei ObjC müssen Sie die Eigenschaft in einer Klasse deklarieren, die behauptet, dem Protokoll zu entsprechen. In Swift können Sie eine Kategorie erstellen, die eine Standardimplementierung einer Funktion definiert. Soweit ich jedoch feststellen konnte, können Sie keine Standardeigenschaft für ein Protokoll haben.
Kendall Helmstetter Gelner
31

Hier ist ein Beispiel von mir, das perfekt funktioniert, vor allem die Protokolldefinition:

@class ExampleClass;

@protocol ExampleProtocol

@required

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

@end

Unten finden Sie ein Arbeitsbeispiel einer Klasse, die dieses Protokoll unterstützt:

#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
quelle
14

Alles, was Sie wirklich tun müssen, ist, a fallen zu lassen

@synthesize title;

in Ihrer Implementierung und Sie sollten fertig sein. Dies funktioniert genauso wie das Einfügen der Eigenschaft in Ihre Klassenschnittstelle.

Bearbeiten:

Möglicherweise möchten Sie dies genauer tun:

@synthesize title = _title;

Dies entspricht der Art und Weise, wie die automatische Synthese von xcode Eigenschaften und Ivars erstellt, wenn Sie die automatische Synthese verwenden. Wenn Ihre Klasse also Eigenschaften aus einem Protokoll und einer Klasse hat, haben einige Ihrer Ivars nicht das andere Format, das sich auswirken könnte Lesbarkeit.

Kevlar
quelle
1
Bist du dir ganz sicher? Ich habe eine optionale Eigenschaft in einem Protokoll festgelegt, und wenn ich sie nur in einer konkreten Klasse @synthesize, die diesem Protokoll entspricht, wird ein Compilerfehler angezeigt, der besagt, dass es sich um eine nicht deklarierte Variable handelt. Keine Tippfehler bestätigt.
Coocoo4Cocoa
Ich bin mir nicht sicher über optionale Eigenschaften, aber eine Sache, die ich vergessen habe zu erwähnen, wie mralex sagte, ist, dass Sie sie an eine Mitgliedsvariable binden müssen, entweder indem Sie diesen Variablentitel benennen oder @synthesize title = myinstancevar sagen;
Kevlar
2
Wenn Sie die moderne Laufzeit verwenden, ist @synthesize alles, was Sie brauchen. Die zugrunde liegenden Ivars werden für Sie erstellt. Wenn Sie auf 32-Bit x86 abzielen, wird der erwähnte Compilerfehler angezeigt, da Sie auf die ältere Laufzeit abzielen.
Jeffrey Harris
1
Die automatische Synthese wurde in Xcode 4.4 eingeführt, aber laut einem Tweet von Graham Lee werden die in Protokollen deklarierten Eigenschaften nicht behandelt. Sie müssen diese Eigenschaften also noch manuell synthetisieren.
cbowns
Dies ist ein großartiger Punkt, wusste nicht, dass das Hinzufügen der synthesizeausreichend war. Cool!
Dan Rosenstark
9

Schauen Sie sich meinen Artikel IMMOBILIEN IM PROTOKOLL an

Angenommen, ich habe MyProtocol, das eine Namenseigenschaft deklariert, und MyClass, das diesem Protokoll entspricht

Dinge, die es wert sind, erwähnt zu werden

  1. Die Bezeichner-Eigenschaft in MyClass deklariert und generiert eine Getter-, Setter- und Backing _identifier-Variable
  2. Die name-Eigenschaft deklariert nur, dass MyClass einen Getter und Setter im Header hat. Es werden keine Getter-, Setter-Implementierungs- und Backing-Variablen generiert.
  3. Ich kann diese Namenseigenschaft nicht erneut deklarieren, da sie bereits vom Protokoll deklariert wurde. Wenn Sie dies tun, wird ein Fehler angezeigt

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

Verwendung der Eigenschaft im Protokoll

Um MyClass mit dieser Namenseigenschaft zu verwenden, müssen wir beides tun

  1. Deklarieren Sie die Eigenschaft erneut (AppDelegate.h führt diesen Vorgang aus).

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
  2. Synthetisieren Sie sich

    @implementation MyClass
    
    @synthesize name;
    
    @end
onmyway133
quelle
In Listen verschachtelte Codeblöcke müssen um acht Leerzeichen pro Zeile eingerückt werden. Es ist eine relativ unbekannte Kuriosität der Markdown-Syntax. Ich habe deine Antwort für dich bearbeitet.
BoltClock
1

Protokollarchitektur

Beispiel: 2 Klassen (Person und Seriennummer) möchten den Dienst von Viewer verwenden ... und müssen ViewerProtocol entsprechen. viewerTypeOfDescription ist eine obligatorische Eigenschaft, der Abonnentenklassen entsprechen müssen.

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

Ein weiteres Beispiel mit Protokollvererbung über Unterklasse

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
quelle
0

Die Variable anObject muss in Ihrer TestProtocolsViewController-Klassendefinition definiert werden. Das Protokoll informiert Sie lediglich darüber, dass sie vorhanden sein sollte.

Die Compilerfehler sagen Ihnen die Wahrheit - die Variable existiert nicht. @properties sind schließlich nur Helfer.

mralex
quelle