Як поводитися з протоколами 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     

Відповіді:


135

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

Це означає, що у вашому класі, що відповідає вашому протоколу, ви повинні зробити все, щоб переконатися, що anObject працює.

@propertyі @synthesizeв основі є два механізми, які створюють код для вас. @propertyпросто говорить, що для цього імені властивості буде метод getter (та / або setter). Тільки цих днів @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

14

все, що вам потрібно зробити насправді - це скинути

@synthesize title;

у вашій реалізації, і ви повинні бути все налаштовані. це працює так само, як просто помістити властивість у інтерфейс вашого класу.

Редагувати:

Ви можете зробити це більш конкретно:

@synthesize title = _title;

Це буде відповідати тому, як автоматичний синтез xcode створює властивості та ivars, якщо ви використовуєте автоматичний синтез, так що, якщо ваш клас має властивості з протоколу та класу, деякі ваші ivars не матимуть іншого формату, який може вплинути читабельність.


1
Ви повністю впевнені? У мене є необов'язкове властивість, встановлене в протоколі, і коли я лише @synthesize це конкретний клас, який відповідає цьому протоколу, я отримую помилку компілятора, стверджуючи, що це недекларована змінна. Не підтверджено помилок друку.
Coocoo4Cocoa

Я не впевнений у необов’язкових властивостях, але одне, що я забув згадати, як сказав mralex, - це те, що вам потрібно прив’язати його до змінної члена, або назвавши цю назву змінної, або сказавши @synthesize title = myinstanvar;
Кевлар

2
Якщо ви використовуєте сучасний час роботи, @synthesize - це все, що вам потрібно, базові ivars будуть створені для вас. Якщо ви орієнтуєтесь на 32-бітний x86, ви згадаєте про помилку компілятора, оскільки ви орієнтуєтесь на застарілий час виконання.
Джефрі Харріс

1
Автоматичний синтез був введений у Xcode 4.4, але, згідно з твітом Грехама Лі , він не охоплює властивості, задекларовані в протоколах. Тому вам потрібно буде вручну синтезувати ці властивості.
cbowns

Це чудовий момент, я не усвідомлював, що додавання цього synthesizeбуло достатньо. Класно!
Дан Розенстарк

9

Погляньте на мою статтю ВЛАСНІСТЬ В ПРОТОКОЛІ

Припустимо, у мене є MyProtocol, який оголошує властивість імені, і MyClass, який відповідає цьому протоколу

Речі, які варто зазначити

  1. Властивість ідентифікатора в MyClass оголошує та генерує змінну getter, setter та backing _identifier
  2. Властивість name лише оголошує, що MyClass має в заголовку getter, setter. Він не генерує getter, реалізацію сетера та резервну змінну.
  3. Я не можу перевизначити це властивість імені, як це вже було заявлено протоколом. Це буде кричати помилку

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

Як використовувати властивість у протоколі

Отже, щоб використовувати MyClass з властивістю цього імені, ми повинні зробити будь-яке

  1. Декларуйте властивість ще раз (AppDelegate.h робить це)

    @interface MyClass : NSObject <MyProtocol>
    
    @property (nonatomic, strong) NSString *name;
    
    @property (nonatomic, strong) NSString *identifier;
    
    @end
  2. Синтезуйте себе

    @implementation MyClass
    
    @synthesize name;
    
    @end

Блоки коду, вкладені в списки, повинні бути відступними на вісім пробілів у рядку. Це відносно невідома дивність синтаксису Маркдаун. Я відредагував вашу відповідь за вас.
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 - це лише помічники.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.