Как работать с протоколами Objective-C, которые содержат свойства?

131

Я видел, как протоколы Objective-C используются следующим образом:

@protocol MyProtocol <NSObject>

@required

@property (readonly) NSString *title;

@optional

- (void) someMethod;

@end

Я видел, как этот формат использовался вместо написания конкретного суперкласса, который расширяют подклассы. Вопрос в том, если вы соблюдаете этот протокол, нужно ли вам синтезировать свойства самостоятельно? Если вы расширяете суперкласс, ответ, очевидно, отрицательный, в этом нет необходимости. Но как поступить со свойствами, которым протокол должен соответствовать?

Насколько я понимаю, вам все равно необходимо объявить переменные экземпляра в файле заголовка объекта, который соответствует протоколу, требующему этих свойств. В таком случае можем ли мы считать, что они всего лишь руководящий принцип? В случае необходимого метода дело обстоит иначе. Компилятор хлопнет вас по запястью, чтобы исключить требуемый метод, указанный в протоколе. Но какова история свойств?

Вот пример, генерирующий ошибку компиляции (Примечание: я обрезал код, который не отражает существующую проблему):

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
источник

Ответы:

135

Протокол просто сообщает всем, кто знает о вашем классе через протокол, что свойство anObjectбудет там. Протоколы не реальны, они не имеют переменных или методов сами по себе - они описывают только определенный набор атрибутов, который является истинным для вашего класса, так что объекты, содержащие ссылки на них, могут использовать их определенным образом.

Это означает, что в вашем классе, который соответствует вашему протоколу, вы должны сделать все, чтобы убедиться, что объект работает.

@propertyи @synthesizeв основе лежат два механизма, которые генерируют для вас код. @propertyпросто говорит, что для этого имени свойства будет метод получения (и / или установки). В наши дни @propertyдостаточно иметь методы и переменную хранилища, созданную для вас системой (раньше вам приходилось добавлять @sythesize). Но у вас должно быть что-то для доступа и хранения переменной.

Кендалл Хельмштеттер Гелнер
источник
80
Для свойств, определенных в протоколе, вам все равно понадобится «@synthesize» даже в современной среде выполнения, или вам нужно продублировать «@property» в определении вашего интерфейса, чтобы получить автосинтез.
Джеффри Харрис
@JeffreyHarris А как насчет того же самого в Swift ??
Каран Алангат
@KaranAlangat - в Swift нет такой вещи, как \ @synthesize, но, как и в ObjC, вам нужно объявить свойство в классе, который утверждает, что соответствует протоколу. В Swift вы можете создать категорию, которая определяет реализацию функции по умолчанию, но, насколько я могу судить, у вас не может быть свойства по умолчанию для протокола.
Кендалл Хельмштеттер Гельнер
31

Вот мой пример, который отлично работает, в первую очередь определение протокола:

@class ExampleClass;

@protocol ExampleProtocol

@required

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

@end

Ниже приведен рабочий пример класса, поддерживающего этот протокол:

#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
источник
14

все, что вам нужно сделать, это бросить

@synthesize title;

в вашей реализации, и все должно быть готово. он работает так же, как просто помещает свойство в интерфейс вашего класса.

Редактировать:

Вы можете сделать это более конкретно:

@synthesize title = _title;

Это будет соответствовать тому, как автоматический синтез xcode создает свойства и ivars, если вы используете автосинтез, поэтому, если ваш класс имеет свойства из протокола и класса, некоторые из ваших ivars не будут иметь другой формат, который может повлиять читаемость.

Кевлар
источник
1
Вы полностью уверены? У меня есть необязательное свойство, установленное в протоколе, и когда я только @ синтезирую его в конкретном классе, который соответствует этому протоколу, я получаю ошибку компилятора, утверждая, что это необъявленная переменная. Опечаток не подтверждено.
Coocoo4Cocoa
Я не уверен в необязательных свойствах, но одну вещь, которую я забыл упомянуть, как сказал mralex, - это то, что вам нужно привязать ее к переменной-члену, либо назвав эту переменную title, либо сказав @synthesize title = myinstancevar;
Кевлар
2
Если вы используете современную среду выполнения, @synthesize - это все, что вам нужно, базовые ivars будут созданы для вас. Если вы ориентируетесь на 32-битную x86, вы получите упомянутую ошибку компилятора, потому что вы ориентируетесь на устаревшую среду выполнения.
Джеффри Харрис,
1
Автоматический синтез был введен в Xcode 4.4, но, согласно твиту Грэма Ли , он не распространяется на свойства, объявленные в протоколах. Поэтому вам все равно придется синтезировать эти свойства вручную.
cbowns
Это отличный момент, не понимал, что добавления synthesizeбыло достаточно. Прохладно!
Дэн Розенстарк,
9

Взгляните на мою статью СОБСТВЕННОСТЬ В ПРОТОКОЛЕ

Предположим, у меня есть MyProtocol, который объявляет свойство name, и MyClass, который соответствует этому протоколу.

Стоит отметить

  1. Свойство идентификатора в MyClass объявляет и генерирует геттер, установщик и резервную переменную _identifier
  2. Свойство name только объявляет, что MyClass имеет геттер, сеттер в заголовке. Он не генерирует геттер, реализацию сеттера и поддерживающую переменную.
  3. Я не могу повторно объявить это свойство name, поскольку оно уже объявлено протоколом. Сделать это будет кричать об ошибке

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

Как использовать свойство в протоколе

Итак, чтобы использовать MyClass с этим свойством name, мы должны сделать либо

  1. Снова объявите свойство (AppDelegate.h делает это)

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
  2. Синтезировать себя

    @implementation MyClass
    
    @synthesize name;
    
    @end
onmyway133
источник
Блоки кода, вложенные в списки, должны иметь отступ по восемь пробелов в строке. Это относительно неизвестная странность синтаксиса Markdown. Я отредактировал ваш ответ для вас.
BoltClock
1

Архитектура протокола

Пример: 2 класса (Person и Serial) хотят использовать службу Viewer ... и должны соответствовать ViewerProtocol. viewerTypeOfDescription - это обязательное свойство, которому должны соответствовать классы подписчика.

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

Другой пример с наследованием протокола над подклассами

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

Переменная anObject должна быть определена в определении класса TestProtocolsViewController, протокол просто информирует вас, что она должна быть там.

Ошибки компилятора говорят вам правду - переменной не существует. В конце концов, @properties - просто помощники.

mralex
источник