Определение категорий для протоколов в 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. Поскольку метод использует только открытые члены протокола, было бы лучше добавить его в протокол.

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

С уважением, Йохен!--4-->

7 ответов


extObjC имеет самый аккуратный материал вы можете сделать с протоколами / категориями... во-первых @concreteprotocol...

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

MyProtocol.h

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

MyProtocol.м

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

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

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

последнее, но не менее важное / не напрямую относится к @protocols is synthesizeAssociation(CLASS, PROPERTY), который " синтезирует свойство для класса, используя связанные объекты. Это в первую очередь полезно для добавления свойств в класс внутри категории. Собственность должна быть объявлена с @property в интерфейсе указанного класса (или категория на нем), и должен иметь тип объекта."

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


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

долгий ответ: как это будет работать? Представьте себе!--6-- > мог бы добавлять методы к существующим протоколам? Как это будет работать? Представьте, что мы хотим добавить другой метод к 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>.


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

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


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

то есть

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

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

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

@interface MyClass <OriginalProtocol,MyExtendedProtocolName>

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


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

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

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

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