Определение категорий для протоколов в Objective-C? - PullRequest
38 голосов
/ 05 октября 2009

В Objective-C я могу добавлять методы к существующим классам с категорией, например,

@interface NSString (MyCategory)
- (BOOL) startsWith: (NSString*) prefix;
@end

Возможно ли это сделать также с протоколами, т. Е. Если был протокол NSString, что-то вроде:

@interface <NSString> (MyCategory)
- (BOOL) startsWith: (NSString*) prefix;
@end

Я хочу сделать это, поскольку у меня есть несколько расширений для NSObject (класса), использующих только общедоступные методы NSObject, и я хочу, чтобы эти расширения также работали с объектами, реализующими протокол.

Чтобы привести еще один пример, что если я захочу написать метод logDescription, который печатает описание объекта в журнал:

- (void) logDescription {
    NSLog(@"%@", [self description]);
}

Конечно, я могу добавить этот метод в NSObject, но есть другие классы, которые не наследуются от NSObject, где я также хотел бы иметь этот метод, например, NSProxy. Поскольку в этом методе используются только открытые члены протокола, было бы лучше добавить его в протокол.

Редактировать: Java 8 теперь имеет это с "виртуальными методами расширения" в интерфейсах: http://cr.openjdk.java.net/~briangoetz/lambda/Defender%20Methods%20v4.pdf. Это именно то, что я хотел бы сделать в Objective-C. Я не видел, чтобы этот вопрос привлек такое большое внимание ...

С уважением, Jochen

Ответы [ 7 ]

25 голосов
/ 05 октября 2009

Краткий ответ: Нет.

Длинный ответ: как это будет работать? Представляете, вы могли бы добавлять методы к существующим протоколам? Как это будет работать? Представьте, что мы хотим добавить еще один метод в NSCoding, скажем, -(NSArray *) codingKeys; Этот метод является обязательным методом, который возвращает массив ключей, используемых для кодирования объекта.

Проблема в том, что существуют существующие классы (например, NSString), которые уже реализуют NSCoding, но не реализуют наш метод codingKeys. Что должно произойти? Как предварительно скомпилированная среда узнает, что делать, когда это обязательное сообщение отправляется в класс, который его не реализует?

Вы могли бы сказать «мы можем добавить определение этого метода через категорию» или «мы можем сказать, что любые методы, добавленные через эти категории протокола, являются явно необязательными». Да, вы можете сделать это и теоретически обойти проблему, которую я описал выше. Но если вы собираетесь это сделать, вы можете сначала сделать его категорией, а затем проверить, чтобы убедиться, что класс respondsToSelector: перед вызовом метода.

22 голосов
/ 14 октября 2009

Хотя верно, что вы не можете определить категории для протоколов (и не захотите, потому что вы ничего не знаете о существующем объекте), вы можете определить категории таким образом, чтобы код относился только к объект данного типа, имеющий требуемый протокол (вроде частичной специализации шаблонов в C ++).

Основное использование для чего-то подобного - это когда вы хотите определить категорию, которая зависит от настроенной версии класса. (Представьте, что у меня есть подклассы UIViewController, которые соответствуют протоколу Foo, то есть они имеют свойство foo, мой код категории может нуждаться в свойстве foo, но я не могу применить его к протоколу Foo, и если я просто его применю для UIViewController код не будет компилироваться по умолчанию, и его принудительная компиляция означает, что кто-то, занимающийся самоанализом или просто испорченный, может вызвать ваш код, который зависит от протокола. Гибридный подход может работать так:

@protocol Foo
- (void)fooMethod

@property (retain) NSString *foo;
@end

@implementation UIViewController (FooCategory)

- (void)fooMethod {
    if (![self conformsToProtocol:@protocol(Foo)]) {
        return;
    }

    UIViewController<Foo> *me = (UIViewController<Foo>*) self;
    // For the rest of the method, use "me" instead of "self"
    NSLog(@"My foo property is \"%@\"", me.foo);
}
@end

При гибридном подходе вы можете написать код только один раз (для каждого класса, который должен реализовывать протокол) и быть уверенным, что это не повлияет на экземпляры класса, которые не соответствуют протоколу.

Недостатком является то, что синтез / определение свойств все еще должно происходить в отдельных подклассах.

18 голосов
/ 02 октября 2013

extObjC содержит элемент NEATEST , который вы можете делать с протоколами / категориями ... в первую очередь @concreteprotocol ...

  • Определяет «конкретный протокол», который может обеспечить реализацию методов по умолчанию в протоколе.
  • Блок @protocol должен существовать в заголовочном файле и соответствующий блок @concreteprotocol в файле реализации.
  • Любой объект, который объявляет себя соответствующим этому протоколу, получит свои реализации методов, но только если уже не существует метода с таким именем.

MyProtocol.h

@protocol MyProtocol 
@required - (void)someRequiredMethod;
@optional - (void)someOptionalMethod;
@concrete - (BOOL)isConcrete;   

MyProtocol.m

 @concreteprotocol(MyProtocol) - (BOOL)isConcrete { return YES; } ...

поэтому объявление объекта MyDumbObject : NSObject <MyProtocol> автоматически вернет YES в isConcrete.

Кроме того, они имеют pcategoryinterface(PROTOCOL,CATEGORY), который "определяет интерфейс для категории с именем CATEGORY в протоколе PROTOCOL". Категории протокола содержат методы, которые автоматически применяются к любому классу, который объявляет себя соответствующим PROTOCOL. "Существует сопутствующий макрос, который вы также должны использовать в своем файле реализации. См. Документы.

Последнее, но НЕ наименьшее / не напрямую , относящееся к @protocols is synthesizeAssociation(CLASS, PROPERTY), который "синтезирует свойство для класса, используя связанные объекты. Это в первую очередь полезно для добавления свойств к классу в категории. СОБСТВЕННОСТЬ должна быть объявлена ​​с @property в интерфейсе указанного класса (или категории на него) и должен иметь тип объекта. "

Так много инструментов в этой библиотеке открывают (повышают) возможности, которые вы можете сделать с ObjC ... от множественного наследования ... до ну, ваше воображение - предел.

7 голосов
/ 05 октября 2009

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

Однако вы можете создать новый протокол, который включает в себя старый протокол с объявлением типа @protocol SpecialObject <NSObject>.

0 голосов
/ 21 июля 2013

Адам Шарп опубликовал решение , которое сработало для меня.

Включает 3 шага:

  1. Определение методов, которые вы хотите добавить в протокол как @optional.
  2. Приведение объектов, которые вы хотите расширить, в соответствие с этим протоколом.
  3. Копирование этих методов в эти объекты во время выполнения.

Проверьте ссылку для получения полной информации.

0 голосов
/ 05 октября 2009

, если вы уже пишете категорию, почему бы просто не добавить определение протокола в заголовок сразу после определения категории?

т.е.

@interface NSString (MyCategory)
- (BOOL) startsWith: (NSString*) prefix;
@end

@protocol MyExtendedProtocolName <NSString>
//Method declarations go here
@end

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

@interface MyClass <OriginalProtocol,MyExtendedProtocolName>

также будьте осторожны при создании подкласса NSString, это кластер, и вы не всегда можете получить ожидаемое поведение.

0 голосов
/ 05 октября 2009

Я думаю, вы можете смешивать термины здесь и там. Расширения, категории, протоколы, интерфейсы и классы - это разные вещи в Objective-C. В языке Objective-C 2.0 Apple очень хорошо описывает различия, включая преимущества и недостатки использования категорий и расширений.

Если подумать, что такое «категория» или «расширение» в концептуальном смысле? Это способ добавления функциональности в класс. В Objective-C протоколы не имеют реализации. Следовательно, как бы вы добавили или расширили реализацию того, что не имеет реализации для начала?

...