Warnung unterdrücken "Kategorie implementiert eine Methode, die auch von ihrer Primärklasse implementiert wird."

98

Ich habe mich gefragt, wie ich die Warnung unterdrücken kann:

Category implementiert eine Methode, die auch von ihrer Primärklasse implementiert wird.

Ich habe dies für eine bestimmte Codekategorie:

+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
    return [self aCustomFontOfSize:fontSize];
}
Doz
quelle
Durch Methode Swizzling. Obwohl ich das nicht tun würde - vielleicht könnten Sie eine UIFont-Unterklasse erstellen, die stattdessen dieselbe Methode überschreibt, und superanders aufrufen .
Alan Zeino
4
Ihr Problem ist nicht die Warnung. Ihr Problem ist, dass Sie denselben Methodennamen haben, was zu Problemen führen wird.
Gnasher729
Unter Überschreiben von Methoden mit Kategorien in Objective-C finden Sie Gründe, warum Sie Methoden mit Kategorien nicht überschreiben sollten, und alternative Lösungen.
Sinnvoll
Wenn Sie eine elegantere Lösung zum Festlegen der anwendungsweiten Schrift kennen, würde ich sie gerne hören!
To1ne

Antworten:

64

Mit einer Kategorie können Sie einer vorhandenen Klasse neue Methoden hinzufügen. Wenn Sie eine bereits in der Klasse vorhandene Methode erneut implementieren möchten, erstellen Sie normalerweise eine Unterklasse anstelle einer Kategorie.

Apple-Dokumentation: Anpassen vorhandener Klassen

Wenn der Name einer in einer Kategorie deklarierten Methode mit einer Methode in der ursprünglichen Klasse oder einer Methode in einer anderen Kategorie in derselben Klasse (oder sogar einer Oberklasse) identisch ist, ist das Verhalten undefiniert, bei welcher Methodenimplementierung verwendet wird Laufzeit.

Zwei Methoden mit genau derselben Signatur in derselben Klasse würden zu unvorhersehbarem Verhalten führen, da nicht jeder Aufrufer angeben kann, welche Implementierung er möchte.

Sie sollten also entweder eine Kategorie verwenden und Methodennamen angeben, die für die Klasse neu und eindeutig sind, oder eine Unterklasse, wenn Sie das Verhalten einer vorhandenen Methode in einer Klasse ändern möchten.

bneely
quelle
1
Ich stimme den oben erläuterten Ideen voll und ganz zu und versuche, ihnen während der Entwicklung zu folgen. Es kann jedoch Fälle geben, in denen Überschreibungsmethoden in der Kategorie angemessen sein können. Zum Beispiel Fälle, in denen Mehrfachvererbung (wie in c ++) oder Schnittstellen (wie in c #) verwendet werden könnten. Ich habe mich in meinem Projekt damit auseinandergesetzt und festgestellt, dass das Überschreiben von Methoden in Kategorien die beste Wahl ist.
Peetonn
4
Dies kann nützlich sein, wenn Sie einen Code testen, der einen Singleton enthält. Im Idealfall sollten Singletons als Protokoll in den Code eingefügt werden, damit Sie die Implementierung ausschalten können. Wenn Sie jedoch bereits eine in Ihren Code eingebettet haben, können Sie in Ihrem Komponententest eine Kategorie des Singletons hinzufügen und die sharedInstance und die Methoden überschreiben, die Sie steuern müssen, um sie in Dummy-Objekte umzuwandeln.
Bandejapaisa
Danke @PsychoDad. Ich habe den Link aktualisiert und ein Zitat aus der Dokumentation hinzugefügt, die für diesen Beitrag relevant ist.
Nur
Sieht gut aus. Bietet Apple eine Dokumentation zum Verhalten bei der Verwendung einer Kategorie mit einem vorhandenen Methodennamen?
jjxtra
1
Super, war mir nicht sicher, ob ich mit Kategorie oder Unterklasse gehen sollte :-)
Kernix
343

Obwohl alles, was gut gesagt wurde, korrekt ist, beantwortet es Ihre Frage, wie die Warnung unterdrückt werden kann, nicht wirklich.

Wenn Sie diesen Code aus irgendeinem Grund haben müssen (in meinem Fall habe ich HockeyKit in meinem Projekt und sie überschreiben eine Methode in einer UIImage-Kategorie [Bearbeiten: dies ist nicht mehr der Fall]) und müssen Sie Ihr Projekt zum Kompilieren bringen können Sie #pragmaAnweisungen verwenden, um die Warnung wie folgt zu blockieren:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"

// do your override

#pragma clang diagnostic pop

Ich habe die Informationen hier gefunden: http://www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html

Ben Baron
quelle
Vielen Dank! Ich sehe also, dass Pragmas auch Warnungen unterdrücken können. :-p
Constantino Tsarouhas
Ja, und obwohl dies LLVM-spezifische Aussagen sind, gibt es ähnliche auch für GCC.
Ben Baron
1
Die Warnung in Ihrem Testprojekt ist eine Linker-Warnung, keine llvm-Compiler-Warnung, daher macht das llvm-Pragma nichts. Sie werden jedoch feststellen, dass Ihr Testprojekt weiterhin mit aktivierter Option "Warnungen als Fehler behandeln" erstellt wird, da es sich um eine Linker-Warnung handelt.
Ben Baron
12
Dies sollte wirklich die akzeptierte Antwort sein, da sie tatsächlich die Frage beantwortet.
Rob Jones
1
Diese Antwort sollte die richtige sein. Auf jeden Fall hat es mehr Stimmen als die als Antwort ausgewählte.
Juan Catalan
20

Eine bessere Alternative (siehe bneelys Antwort darauf, warum diese Warnung Sie vor einer Katastrophe bewahrt) ist die Verwendung von Methoden-Swizzling. Durch die Verwendung von Methoden-Swizzling können Sie eine vorhandene Methode aus einer Kategorie ersetzen, ohne die Unsicherheit darüber zu haben, wer "gewinnt", und gleichzeitig die Möglichkeit zu bewahren, die alte Methode aufzurufen. Das Geheimnis besteht darin, der Überschreibung einen anderen Methodennamen zu geben und sie dann mithilfe von Laufzeitfunktionen auszutauschen.

#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);
}

Definieren Sie dann Ihre benutzerdefinierte Implementierung:

+ (UIFont *)mySystemFontOfSize:(CGFloat)fontSize {
...
}

Überschreiben Sie die Standardimplementierung mit Ihrer:

MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));
Sanjit Saluja
quelle
10

Versuchen Sie dies in Ihrem Code:

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

UPDATE2: Fügen Sie dieses Makro hinzu

#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
Vitaliy Gervazuk
quelle
1
Dies ist kein vollständiges Beispiel. Kein Makro mit dem Namen EXCHANGE_METHOD wird tatsächlich durch die Laufzeit von Objective-C definiert.
Richard J. Ross III
@Vitaly Stil -1. Diese Methode ist für den Klassentyp nicht implementiert. Welches Framework verwenden Sie?
Richard J. Ross III
Entschuldigung nochmal, probieren Sie es aus Ich habe die Datei NSObject + MethodExchange
Vitaliy Gervazuk
Warum sollte man sich bei der Kategorie auf NSObject überhaupt mit dem Makro beschäftigen? Warum nicht einfach mit der 'exchangeMethod' bummeln?
Hvanbrug
5

Sie können die Methoden-Swizzling verwenden, um diese Compiler-Warnung zu unterdrücken. Hier ist, wie ich die Methode Swizzling zum Zeichnen von Rändern in einem UITextField implementiert habe, wenn wir einen benutzerdefinierten Hintergrund mit UITextBorderStyleNone verwenden:

#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
Say2Manuj
quelle
2

Übergeordnete Eigenschaften gelten für eine Klassenerweiterung (anonyme Kategorie), jedoch nicht für eine reguläre Kategorie.

Laut Apple Docs, die eine Klassenerweiterung (anonyme Kategorie) verwenden, können Sie eine private Schnittstelle zu einer öffentlichen Klasse erstellen, sodass die private Schnittstelle die öffentlich zugänglichen Eigenschaften überschreiben kann. dh Sie können eine Eigenschaft von readonly in readwrite ändern.

Ein Anwendungsfall hierfür ist, wenn Sie Bibliotheken schreiben, die den Zugriff auf öffentliche Eigenschaften einschränken, während dieselbe Eigenschaft einen vollständigen Lese- / Schreibzugriff innerhalb der Bibliothek benötigt.

Apple Docs Link: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

Suchen Sie nach " Klassenerweiterungen verwenden, um private Informationen auszublenden ".

Diese Technik gilt also für eine Klassenerweiterung, jedoch nicht für eine Kategorie.

Kris Subramanian
quelle
1

Kategorien sind eine gute Sache, aber sie können missbraucht werden. Wenn Sie Kategorien schreiben, sollten Sie bestehende Methoden grundsätzlich NICHT erneut implementieren. Dies kann zu seltsamen Nebenwirkungen führen, da Sie jetzt Code neu schreiben, von dem eine andere Klasse abhängt. Sie könnten eine bekannte Klasse brechen und Ihren Debugger auf den Kopf stellen. Es ist einfach schlechte Programmierung.

Wenn Sie es tun müssen, sollten Sie es wirklich unterordnen.

Dann der Vorschlag des Swizzling, das ist ein großes NEIN-NEIN-NEIN für mich.

Das Swizzing zur Laufzeit ist ein komplettes NO-NO-NO.

Sie möchten, dass eine Banane wie eine Orange aussieht, aber nur zur Laufzeit? Wenn Sie eine Orange wollen, dann schreiben Sie eine Orange.

Lass keine Banane aussehen und benimm dich wie eine Orange. Und noch schlimmer: Verwandeln Sie Ihre Banane nicht in einen Geheimagenten, der Bananen weltweit leise sabotiert, um Orangen zu unterstützen.

Huch!

Leander
quelle
3
Das Swizzing zur Laufzeit kann jedoch hilfreich sein, um Verhaltensweisen in Testumgebungen zu verspotten.
Ben G
2
Obwohl humorvoll, kann Ihre Antwort nicht mehr als sagen, dass alle möglichen Methoden schlecht sind. Die Natur des Tieres ist, dass man manchmal wirklich keine Unterklasse haben kann, so dass man eine Kategorie hat und besonders wenn man den Code für die Klasse, die man kategorisiert, nicht besitzt, muss man manchmal das Swizzle machen und es eine unerwünschte Methode zu sein ist nicht relevant.
Hvanbrug
1

Ich hatte dieses Problem, als ich eine Delegatmethode in einer Kategorie anstelle der Hauptklasse implementierte (obwohl es keine Implementierung der Hauptklasse gab). Die Lösung für mich bestand darin, die Header-Datei der Hauptklasse in die Header-Datei der Kategorie zu verschieben. Dies funktioniert einwandfrei

Gheese
quelle