Подавить предупреждение "Категория реализует метод, который также будет реализован его основным классом"
Мне было интересно, как подавить предупреждение:
Категория реализует метод, который также будет реализован его основным классом.
У меня есть это для конкретной категории кода:
+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
return [self aCustomFontOfSize:fontSize];
}
8 ответов
Категория позволяет добавлять новые методы в существующий класс. Если вы хотите переопределить метод, который уже существует в классе, вы обычно создаете подкласс вместо категории.
Документация Apple: настройка существующих классов
Если имя метода, объявленного в категории, совпадает с именем метода в исходном классе или методом в другой категории того же класса (или даже суперкласса), поведение не определено относительно того, какая реализация метода используется в во время выполнения.
Два метода с точно такой же сигнатурой в одном и том же классе могут привести к непредсказуемому поведению, поскольку каждый вызывающий объект не может указать, какую реализацию они хотят.
Таким образом, вы должны либо использовать категорию и предоставить имена методов, которые являются новыми и уникальными для класса, или подкласс, если вы хотите изменить поведение существующего метода в классе.
Несмотря на то, что все, что было сказано, верно, на самом деле это не отвечает на ваш вопрос о том, как подавить предупреждение.
Если вам по какой-то причине нужен этот код (в моем случае у меня есть HockeyKit в моем проекте, и они переопределяют метод в категории UIImage [править: это уже не так]), и вам нужно получить свой проект для компиляции, ты можешь использовать #pragma
заявления, чтобы заблокировать предупреждение, например, так:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
// do your override
#pragma clang diagnostic pop
Я нашел информацию здесь: http://www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html
Лучшая альтернатива (см. Ответ Бнили, почему это предупреждение спасает вас от катастрофы) - использовать метод Swizzling. Используя метод swizzling, вы можете заменить существующий метод из категории, не зная, кто "победит", и при этом сохраняя возможность вызова старого метода. Секрет в том, чтобы присвоить переопределению другое имя метода, а затем поменять их местами с помощью функций времени выполнения.
#import <objc/runtime.h>
#import <objc/message.h>
void MethodSwizzle(Class c, SEL orig, SEL new) {
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
Затем определите вашу пользовательскую реализацию:
+ (UIFont *)mySystemFontOfSize:(CGFloat)fontSize {
...
}
Переопределите реализацию по умолчанию своей:
MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));
Попробуйте это в своем коде:
+(void)load{
EXCHANGE_METHOD(Method1, Method1Impl);
}
ОБНОВЛЕНИЕ 2: Добавить этот макрос
#import <Foundation/Foundation.h>
#define EXCHANGE_METHOD(a,b) [[self class]exchangeMethod:@selector(a) withNewMethod:@selector(b)]
@interface NSObject (MethodExchange)
+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel;
@end
#import <objc/runtime.h>
@implementation NSObject (MethodExchange)
+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel{
Class class = [self class];
Method origMethod = class_getInstanceMethod(class, origSel);
if (!origMethod){
origMethod = class_getClassMethod(class, origSel);
}
if (!origMethod)
@throw [NSException exceptionWithName:@"Original method not found" reason:nil userInfo:nil];
Method newMethod = class_getInstanceMethod(class, newSel);
if (!newMethod){
newMethod = class_getClassMethod(class, newSel);
}
if (!newMethod)
@throw [NSException exceptionWithName:@"New method not found" reason:nil userInfo:nil];
if (origMethod==newMethod)
@throw [NSException exceptionWithName:@"Methods are the same" reason:nil userInfo:nil];
method_exchangeImplementations(origMethod, newMethod);
}
@end
Вы можете использовать метод swizzling для подавления этого предупреждения компилятора. Вот как я реализовал метод Swizzling для рисования полей в UITextField, когда мы используем пользовательский фон с UITextBorderStyleNone:
#import <UIKit/UIKit.h>
@interface UITextField (UITextFieldCatagory)
+(void)load;
- (CGRect)textRectForBoundsCustom:(CGRect)bounds;
- (CGRect)editingRectForBoundsCustom:(CGRect)bounds;
@end
#import "UITextField+UITextFieldCatagory.h"
#import <objc/objc-runtime.h>
@implementation UITextField (UITextFieldCatagory)
+(void)load
{
Method textRectForBounds = class_getInstanceMethod(self, @selector(textRectForBounds:));
Method textRectForBoundsCustom = class_getInstanceMethod(self, @selector(textRectForBoundsCustom:));
Method editingRectForBounds = class_getInstanceMethod(self, @selector(editingRectForBounds:));
Method editingRectForBoundsCustom = class_getInstanceMethod(self, @selector(editingRectForBoundsCustom:));
method_exchangeImplementations(textRectForBounds, textRectForBoundsCustom);
method_exchangeImplementations(editingRectForBounds, editingRectForBoundsCustom);
}
- (CGRect)textRectForBoundsCustom:(CGRect)bounds
{
CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
return inset;
}
- (CGRect)editingRectForBoundsCustom:(CGRect)bounds
{
CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
return inset;
}
@end
Свойства переопределения действительны для расширения класса (анонимной категории), но не для обычной категории.
В соответствии с Apple Docs, использующими расширение класса (анонимная категория), вы можете создать закрытый интерфейс для открытого класса, чтобы закрытый интерфейс мог переопределять открытые для доступа свойства. т.е. вы можете изменить свойство с readonly на readwrite.
Вариант использования для этого - когда вы пишете библиотеки, которые ограничивают доступ к публичным свойствам, в то время как это же свойство требует полного доступа на чтение и чтение в библиотеке.
Ссылка на Apple Docs: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html
Ищите "Используйте расширения класса, чтобы скрыть личную информацию".
Таким образом, этот метод действителен для расширения класса, но не для категории.
Категории - это хорошо, но ими можно злоупотреблять. При написании категорий вы должны, как правило, НЕ переосмысливать существующие методы. Это может вызвать странный побочный эффект, так как вы сейчас переписываете код, от которого зависит другой класс. вы можете сломать известный класс и в итоге вывернуть свой отладчик наизнанку. Это просто плохое программирование.
Если вам нужно сделать это, вам действительно следует создать его подкласс.
Тогда предложение мурлыкать, это большое НЕТ-НЕТ-НЕТ для меня.
Размахивать им во время выполнения - это полное НЕТ-НЕТ-НЕТ.
Вы хотите, чтобы банан выглядел как апельсин, но только во время выполнения? Если вы хотите апельсин, то напишите апельсин.
Не делайте банановый вид и не ведите себя как апельсин. И что еще хуже: не превращайте свой банан в секретного агента, который будет тихо саботировать бананы по всему миру в поддержку апельсинов.
Хлоп!
У меня была эта проблема, когда я реализовал метод делегата в категории, а не в главном классе (хотя реализации основного класса не было). Решение для меня было переместить из заголовочного файла основного класса в заголовочный файл категории