Jak obsługiwać protokoły Objective-C, które zawierają właściwości?

131

Widziałem, jak protokoły Objective-C są wykorzystywane w następujący sposób:

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

Widziałem ten format używany zamiast pisania konkretnej nadklasy, która rozszerza podklasy. Pytanie brzmi, czy jeśli dostosujesz się do tego protokołu, czy musisz samodzielnie zsyntetyzować właściwości? Jeśli rozszerzasz superklasę, odpowiedź oczywiście brzmi: nie, nie musisz. Ale jak radzić sobie z właściwościami, z którymi protokół wymaga zgodności?

W moim rozumieniu nadal musisz zadeklarować zmienne instancji w pliku nagłówkowym obiektu, który jest zgodny z protokołem wymagającym tych właściwości. W takim razie czy możemy założyć, że są one tylko zasadą przewodnią? W zasadzie to samo nie dotyczy wymaganej metody. Kompilator uderzy cię w nadgarstek za wykluczenie wymaganej metody wymienionej w protokole. Jaka jest jednak historia nieruchomości?

Oto przykład, który generuje błąd kompilacji (Uwaga: przyciąłem kod, który nie odzwierciedla problemu):

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
źródło

Odpowiedzi:

135

Protokół po prostu mówi wszystkim, którzy wiedzą o twojej klasie poprzez protokół, że nieruchomość anObjecttam będzie. Protokoły nie są rzeczywiste, same nie mają zmiennych ani metod - opisują tylko określony zestaw atrybutów, które są prawdziwe dla Twojej klasy, tak aby obiekty zawierające odniesienia do nich mogły ich używać w określony sposób.

Oznacza to, że w Twojej klasie, która jest zgodna z Twoim protokołem, musisz zrobić wszystko, aby upewnić się, że anObject działa.

@propertyi @synthesizesą w sercu dwa mechanizmy, które generują kod. @propertymówi tylko, że będzie metoda pobierająca (i / lub ustawiająca) dla tej nazwy właściwości. W dzisiejszych czasach @propertywystarczy mieć również metody i zmienną pamięci utworzoną przez system (wcześniej trzeba było dodawać @sythesize). Ale musisz mieć coś, aby uzyskać dostęp i przechowywać zmienną.

Kendall Helmstetter Gelner
źródło
80
W przypadku właściwości zdefiniowanych w protokole nadal potrzebujesz „@synthesize” nawet w nowoczesnym środowisku wykonawczym lub musisz zduplikować „@property” w definicji interfejsu, aby uzyskać automatyczną syntezę.
Jeffrey Harris
@JeffreyHarris A co z tym samym w Swift?
Karan Alangat
@KaranAlangat - nie ma czegoś takiego jak \ @synthesize w Swift, ale tak jak ObjC musisz zadeklarować właściwość w klasie, która twierdzi, że jest zgodna z protokołem. W Swift możesz utworzyć kategorię, która definiuje domyślną implementację funkcji, ale z tego, co wiem, nie możesz mieć domyślnej właściwości dla protokołu.
Kendall Helmstetter Gelner
31

Oto mój przykład, który działa doskonale, przede wszystkim definicja protokołu:

@class ExampleClass;

@protocol ExampleProtocol

@required

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

@end

Poniżej znajduje się roboczy przykład klasy obsługującej ten protokół:

#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
źródło
14

wszystko, co naprawdę musisz zrobić, to upuścić plik

@synthesize title;

w implementacji i wszystko powinno być gotowe. działa tak samo, jak zwykłe umieszczenie właściwości w interfejsie klasy.

Edytować:

Możesz to zrobić bardziej szczegółowo:

@synthesize title = _title;

Będzie to zgodne ze sposobem, w jaki automatyczna synteza xcode tworzy właściwości i ivars, jeśli używasz automatycznej syntezy, więc jeśli twoja klasa ma właściwości z protokołu i klasy, niektóre z twoich ivarów nie będą miały innego formatu, który może mieć czytelność.

Kevlar
źródło
1
Czy jesteś całkowicie pewien? Mam opcjonalną właściwość ustawioną w protokole, a kiedy @synthesize go tylko w konkretnej klasie zgodnej z tym protokołem - otrzymuję błąd kompilatora, twierdzący, że jest to niezadeklarowana zmienna. Brak potwierdzonych literówek.
Coocoo4Cocoa
Nie jestem pewien co do właściwości opcjonalnych, ale zapomniałem wspomnieć, jak powiedział mralex, że musisz powiązać ją ze zmienną składową, albo przez nazwanie tej zmiennej tytułu, albo mówiąc @synthesize title = myinstancevar;
Kevlar
2
Jeśli korzystasz z nowoczesnego środowiska wykonawczego, @synthesize jest wszystkim, czego potrzebujesz, podstawowe ivars zostaną stworzone dla Ciebie. Jeśli celujesz w 32-bitową architekturę x86, pojawi się wspomniany błąd kompilatora, ponieważ celem jest starsze środowisko uruchomieniowe.
Jeffrey Harris
1
Automatyczna synteza została wprowadzona w Xcode 4.4, ale według tweeta Grahama Lee nie obejmuje ona właściwości zadeklarowanych w protokołach. Więc nadal będziesz musiał ręcznie zsyntetyzować te właściwości.
cbowns,
To świetna uwaga, nie zdawałem sobie sprawy, że dodanie synthesizewystarczy. Fajne!
Dan Rosenstark
9

Spójrz na mój artykuł WŁASNOŚĆ W PROTOKOLU

Załóżmy, że mam MyProtocol, która deklaruje właściwość name i MyClass, która jest zgodna z tym protokołem

Rzeczy warte odnotowania

  1. Właściwość identifier w MyClass deklaruje i generuje zmienną pobierającą, ustawiającą i zapasową _identifier
  2. Właściwość name tylko deklaruje, że MyClass ma metodę pobierającą, ustawiającą w nagłówku. Nie generuje metody pobierającej, implementacji ustawiającej i zmiennej bazowej.
  3. Nie mogę ponownie zadeklarować tej właściwości nazwy, ponieważ została już zadeklarowana przez protokół. Zrobienie tego spowoduje wyświetlenie błędu

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

Jak używać właściwości w protokole

Tak więc, aby użyć MyClass z tą właściwością name, musimy zrobić jedno i drugie

  1. Ponownie zadeklaruj właściwość (AppDelegate.h robi to w ten sposób)

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
    
  2. Zsyntetyzuj się

    @implementation MyClass
    
    @synthesize name;
    
    @end
    
onmyway133
źródło
Bloki kodu zagnieżdżone w listach muszą być wcięte o osiem spacji w każdym wierszu. Jest to stosunkowo nieznana osobliwość składni języka Markdown. Zredagowałem twoją odpowiedź.
BoltClock
1

Architektura protokołów

Przykład: 2 klasy (Person i Serial) chcą korzystać z usługi Viewer ... i muszą być zgodne z ViewerProtocol. viewerTypeOfDescription jest obowiązkową właściwością, której klasy subskrybentów muszą być zgodne.

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

Inny przykład z dziedziczeniem protokołu po podklasach

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
źródło
0

Zmienna anObject musi być zdefiniowana w Twojej definicji klasy TestProtocolsViewController, a protokół informuje Cię tylko, że powinna tam być.

Błędy kompilatora mówią prawdę - zmienna nie istnieje. W końcu @properties są tylko pomocnikami.

mralex
źródło