Придушити попередження "Категорія реалізує метод, який також буде реалізований своїм основним класом"


98

Мені було цікаво, як придушити попередження:

Категорія реалізує метод, який також буде реалізований його основним класом.

Я маю це для конкретної категорії коду:

+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
    return [self aCustomFontOfSize:fontSize];
}

За методом шипіння. Хоча я би цього не робив - можливо, ви могли б створити підклас UIFont, який замінює той самий метод, і зателефонувати superінакше.
Алан Зейно

4
Ваша проблема - це не попередження. Ваша проблема полягає в тому, що у вас однакова назва методу, що призведе до проблем.
gnasher729

Див. Розділи Переважні методи, що використовують категорії в Objective-C, з причин, чому не слід переосмислювати методи з використанням категорій, та альтернативних рішень.
Чуйний

Якщо ви знаєте більш елегантне рішення для встановлення шрифту для додатків, я дуже хотів би це почути!
To1ne

Відповіді:


64

Категорія дозволяє додавати нові методи до наявного класу. Якщо ви хочете повторно реалізувати метод, який вже існує в класі, ти зазвичай створюєш підклас замість категорії.

Документація Apple: Налаштування існуючих класів

Якщо назва методу, оголошеного в категорії, збігається з методом в початковому класі або методом в іншій категорії того ж класу (або навіть суперкласу), поведінка не визначена щодо того, який спосіб використання використовується в час виконання.

Два методи з точно таким же підписом у тому ж класі призвели б до непередбачуваної поведінки, оскільки кожен абонент не може вказати, яку реалізацію вони хочуть.

Отже, вам слід або використовувати категорію та надати назви методів, які є новими та унікальними для класу, або підклас, якщо ви хочете змінити поведінку існуючого методу в класі.


1
я цілком погоджуюся з тими ідеями, поясненими вище, і намагаюся дотримуватися їх під час розвитку. але все ж можуть бути випадки, коли методи переосмислення категорії можуть бути доречними. наприклад, випадки, коли можна було використовувати декілька успадкованих (наприклад, у c ++) або інтерфейсів (як у c #). я просто зіткнувся з цим у своєму проекті і зрозумів, що переважні методи в категоріях - найкращий вибір.
peetonn

4
Це може бути корисно при тестуванні одиничного коду, в якому є синглтон. В ідеалі одиночні кнопки повинні бути введені в код як протокол, що дозволяє вимкнути реалізацію. Але якщо у вас вже є вбудований всередині вашого коду, ви можете додати категорію синглтона в тест одиниці і замінити sharedInstance та методи, якими керувати, щоб перетворити їх на манекенні об’єкти.
bandejapaisa

Дякую @PsychoDad Я оновив посилання та додав цитату з документації, що стосується цієї публікації.
bneely

Виглядає добре. Чи надає Apple документацію щодо поведінки використання категорії з існуючим ім'ям методу?
jjxtra

1
приголомшливий, не був впевнений, чи варто мені перейти з категорією чи підкласом :-)
kernix

343

Незважаючи на те, що все, що було сказано, є правильним, воно насправді не відповідає на ваше запитання, як придушити попередження.

Якщо вам доведеться мати цей код з якихось причин (у моєму випадку я маю 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


Дуже дякую! Отже, я бачу, що прагми теж можуть придушити попередження. :-p
Константіно Царухас

Так, і хоча це специфічні для LLVM твердження, є і подібні для GCC.
Бен Барон

1
Попередження у вашому тестовому проекті - це попередження про посилання, а не попередження компілятора llvm, отже, прагма llvm нічого не робить. Однак ви помітите, що ваш тестовий проект все ще створюється з включеним "поводженням з попередженнями як помилками", оскільки це попередження про посилання.
Бен Барон

12
Це дійсно має бути прийнятою відповіддю, враховуючи, що вона фактично відповідає на питання.
Роб Джонс

1
Ця відповідь повинна бути правильною. У всякому разі, в ньому більше голосів, ніж той, який вибрали як відповідь.
Хуан Каталонський

20

Кращою альтернативою (див. Відповідь bneely на те, чому це попередження рятує вас від катастрофи), - це використовувати метод 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:));

10

Спробуйте це у своєму коді:

+(void)load{
    EXCHANGE_METHOD(Method1, Method1Impl);
}

UPDATE2: Додайте цей макрос

#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

1
Це не повний приклад. Жоден макрос з назвою EXCHANGE_METHOD насправді не визначений програмою target-c.
Річард Дж. Росс III

@Vitaly stil -1. цей метод не реалізований для типу класу. Яку основу ви використовуєте?
Річард Дж. Росс III

Вибачте ще раз, спробуйте це, я створив файл NSObject + MethodExchange
Віталій Гервазук

Чому категорія на NSObject чому навіть турбуватися з макросом? Чому б просто не переплутати "обмінний метод"?
hvanbrug

5

Для придушення цього попередження компілятора ви можете скористатися методом шипучих перешкод. Ось як я реалізував метод шипування для малювання полів у 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

2

Властивості перевищення верхової їзди дійсні для розширення класу (анонімна категорія), але не для звичайної категорії.

Згідно з документами Apple Docs, використовуючи розширення класу (Anonymous Category), ви можете створити приватний інтерфейс для загальнодоступного класу, таким чином, щоб приватний інтерфейс міг змінити загальнодоступні властивості. тобто ви можете змінити властивість з readonly на readwrite.

Випадком для цього є коли ви пишете бібліотеки, які обмежують доступ до загальнодоступних властивостей, тоді як для цього ж властивості потрібен повний доступ для запису зчитування в бібліотеці.

Посилання Apple Docs: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

Пошук " Використовувати розширення класів для приховування приватної інформації ".

Тож ця методика дійсна для розширення класу, але не для категорії.


1

Категорії - це добре, але ними можна зловживати. При написанні категорій ви, як правило, НЕ повинні повторно застосовувати методи виходу. Це може спричинити дивні побічні ефекти, оскільки ви переписуєте код, який залежить від іншого класу. ви можете зламати відомий клас і перетворити налагоджувач всередину. Це просто погане програмування.

Якщо вам потрібно це, ви дійсно повинні підкласифікувати його.

Тоді пропозиція шиплячи, це для мене великий НІ-НІ-НІ.

Переміщення його під час виконання - це повна NO-NO-NO.

Хочете, щоб банан був схожий на апельсин, але лише під час виконання? Якщо ви хочете помаранчевий, то напишіть апельсин.

Не погляньте на банан і поводьтесь як апельсин. І ще гірше: не перетворюйте свій банан на секретний агент, який буде тихо саботувати банани у всьому світі на підтримку апельсинів.

Yikes!


3
Швидкість руху під час виконання може бути корисною для глузування з поведінки в тестовому середовищі.
Бен Г

2
Незважаючи на жартівливість, ваша відповідь насправді не більше, ніж скажіть, що всі можливі методи погані. Природа звіра полягає в тому, що іноді ти справді не можеш підклас, тому ти залишаєшся з категорією, особливо, якщо ти не володієш кодом для класу, який ти класифікуєш, іноді потрібно робити швидкість, і це бути небажаним методом не має значення.
hvanbrug

1

У мене була ця проблема, коли я реалізував метод делегата в категорії, а не в основному класі (навіть не було реалізації основного класу). Для мене рішення було перенести з файлу заголовка основного класу в файл заголовка категорії. Це добре працює

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.