Определение категорий для протоколов в Objective-C?

В 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. Я не видел этот вопрос, заслуживающий такого большого внимания...

С уважением, Йохен

7 ответов

Решение

У extObjC есть САМЫЕ НЕОБХОДИМЫЕ вещи, которые вы можете делать с протоколами / категориями... @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 является synthesizeAssociation(CLASS, PROPERTY), который "синтезирует свойство для класса, используя связанные объекты. Это в первую очередь полезно для добавления свойств к классу в категории. СОБСТВЕННОСТЬ должна быть объявлена ​​с @property в интерфейсе указанного класса (или категории на нем) и должен иметь тип объекта. "

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

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

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

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

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

Хотя верно, что вы не можете определить категории для протоколов (и не захотите, потому что вы ничего не знаете о существующем объекте), вы можете определить категории таким образом, чтобы код применялся только к объекту данный тип, имеющий требуемый протокол (вроде частичной специализации шаблонов в 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

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

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

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

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

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

Он включает в себя 3 шага:

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

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

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

т.е.

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

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

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

@interface MyClass <OriginalProtocol,MyExtendedProtocolName>

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

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

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

Другие вопросы по тегам