Определение категорий протоколов в 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. Я не видел, чтобы этот вопрос привлек это внимание...
С уважением,
Jochen
Ответы
Ответ 1
extObjC имеет НЕОБХОДИМЫЙ материал, который вы можете делать с протоколами/категориями... first off is @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 должен быть объявлен с помощью @property
в интерфейсе указанного класса (или категории на нем) и должен быть типа объекта.
Так много инструментов в этой библиотеке открывают (способ) вещи, которые вы можете делать с ObjC... из множественного наследования... для того, чтобы ваше воображение было пределом.
Ответ 2
Короткий ответ: Нет.
Длинный ответ: как это будет работать? Представьте, что вы могли бы добавлять методы к существующим протоколам? Как это будет работать? Представьте, что мы хотели добавить еще один метод в NSCoding, скажем -(NSArray *) codingKeys;
Этот метод является обязательным методом, который возвращает массив ключей, используемых для кодирования объекта.
Проблема в том, что существуют существующие классы (например, NSString), которые уже реализуют NSCoding, но не реализуют наш метод codingKeys
. Что должно произойти? Каким образом предварительно скомпилированная структура знает, что делать, когда это необходимое сообщение отправляется классу, который его не реализует?
Можно сказать: "мы можем добавить определение этого метода через категорию" или "мы могли бы сказать, что любые методы, добавленные через эти категории протоколов, явно необязательны". Да, вы могли бы это сделать и теоретически обойти проблему, описанную выше. Но если вы собираетесь это сделать, вы можете просто сделать его категорией в первую очередь, а затем проверить, чтобы убедиться, что класс respondsToSelector:
перед вызовом метода.
Ответ 3
Хотя верно, что вы не можете определять категории для протоколов (и не хотели бы, потому что вы ничего не знаете о существующем объекте), вы можете определять категории таким образом, чтобы код применялся только к объект данного типа, который имеет желаемый протокол (вроде как частичная специализированная спецификация С++).
Основное использование для чего-то вроде этого - это когда вы хотите определить категорию, которая зависит от настроенной версии класса. (Представьте, что у меня есть подклассы 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
С помощью гибридного подхода вы можете написать код только один раз (для каждого класса, который должен реализовывать протокол), и убедитесь, что он не будет влиять на экземпляры класса, которые не соответствуют протоколу.
Недостатком является то, что синтез/определение свойств еще должно произойти в отдельных подклассах.
Ответ 4
Это не имеет смысла делать так, поскольку протокол не может реально реализовать этот метод. Протокол - это способ объявить, что вы поддерживаете некоторые методы. Добавление метода в этот список за пределами протокола означает, что все "соответствующие" классы случайно объявляют новый метод, даже если они не реализуют его. Если какой-то класс реализовал протокол NSObject, но не спустился с NSObject, а затем вы добавили метод к протоколу, который нарушил бы соответствие класса.
Однако вы можете создать новый протокол, который включает старый, с объявлением вроде @protocol SpecialObject <NSObject>
.
Ответ 5
Я думаю, что вы можете смешивать термины здесь и там. Расширения, категории, протоколы, интерфейсы и классы - это разные вещи в Objective-C. В Язык Objective-C 2.0 Apple описывает различия очень хорошо, включая преимущества и недостатки использования категорий и расширений.
Если вы думаете об этом, что такое "категория" или "расширение" в концептуальном смысле? Это способ добавления функциональности в класс. В Objective-C протоколы не предназначены для реализации. Поэтому, как бы вы могли добавить или расширить реализацию чего-то, что не имеет реализации, начиная с?
Ответ 6
если вы уже пишете категорию, почему бы просто не добавить определение протокола в заголовок сразу после определения категории?
то есть.
@interface NSString (MyCategory)
- (BOOL) startsWith: (NSString*) prefix;
@end
@protocol MyExtendedProtocolName <NSString>
//Method declarations go here
@end
таким образом любой класс, который импортирует заголовок категории, также получит определение протокола, и вы можете добавить его в свой класс.
@interface MyClass <OriginalProtocol,MyExtendedProtocolName>
также, будьте осторожны при подклассификации NSString, это кластер, и вы не всегда можете получить поведение, которое ожидаете.
Ответ 7
Adam Sharp опубликовал решение, которое сработало для меня.
Он включает в себя 3 этапа:
- Определение методов, которые вы хотите добавить как
@optional
в протокол.
- Создание объектов, которые вы хотите продлить, соответствует этому протоколу.
- Копирование этих методов в те объекты во время выполнения.
Ознакомьтесь со ссылкой для получения полной информации.