Supprimer l'avertissement "La catégorie met en œuvre une méthode qui sera également implémentée par sa classe principale"


98

Je me demandais comment supprimer l'avertissement:

Category met en œuvre une méthode qui sera également implémentée par sa classe primaire.

J'ai ceci pour une catégorie de code spécifique:

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

Par méthode swizzling. Bien que je ne le fasse pas, vous pourriez peut-être créer une sous-classe UIFont qui remplace la même méthode à la place et appeler superautrement.
Alan Zeino

4
Votre problème n'est pas l'avertissement. Votre problème est que vous avez le même nom de méthode, ce qui va entraîner des problèmes.
gnasher729

Consultez Remplacement de méthodes utilisant des catégories dans Objective-C pour connaître les raisons pour lesquelles vous ne devez pas remplacer les méthodes à l'aide de catégories et pour des solutions alternatives.
Senseful

Si vous connaissez une solution plus élégante pour définir la police à l'échelle de l'application, j'aimerais vraiment l'entendre!
To1ne le

Réponses:


64

Une catégorie vous permet d'ajouter de nouvelles méthodes à une classe existante. Si vous souhaitez réimplémenter une méthode qui existe déjà dans la classe, vous créez généralement une sous-classe au lieu d'une catégorie.

Documentation Apple: Personnalisation des classes existantes

Si le nom d'une méthode déclarée dans une catégorie est le même qu'une méthode dans la classe d'origine, ou une méthode dans une autre catégorie sur la même classe (ou même une superclasse), le comportement n'est pas défini quant à l'implémentation de méthode utilisée à Durée.

Deux méthodes avec exactement la même signature dans la même classe entraîneraient un comportement imprévisible, car chaque appelant ne peut pas spécifier l'implémentation qu'il souhaite.

Par conséquent, vous devez soit utiliser une catégorie et fournir des noms de méthode nouveaux et uniques pour la classe, soit une sous-classe si vous souhaitez modifier le comportement d'une méthode existante dans une classe.


1
Je suis totalement d'accord avec ces idées expliquées ci-dessus et j'essaie de les suivre pendant le développement. mais il peut néanmoins y avoir des cas où des méthodes de substitution dans la catégorie pourraient être appropriées. par exemple, les cas où l'héritage multiple (comme en c ++) ou des interfaces (comme en c #) pourraient être utilisés. juste confronté à cela dans mon projet et réalisé que les méthodes de substitution dans les catégories sont le meilleur choix.
peetonn

4
Cela peut être utile lors du test unitaire de code contenant un singleton. Idéalement, les singletons doivent être injectés dans le code en tant que protocole, vous permettant de changer l'implémentation. Mais si vous en avez déjà un intégré dans votre code, vous pouvez ajouter une catégorie du singleton dans votre test unitaire et remplacer sharedInstance et les méthodes que vous contrôlez pour les transformer en objets factices.
bandejapaisa

Merci @PsychoDad. J'ai mis à jour le lien et ajouté une citation de la documentation pertinente pour cet article.
bneely

Cela semble bon. Apple fournit-il une documentation sur le comportement de l'utilisation d'une catégorie avec un nom de méthode existant?
jjxtra

1
génial, je ne savais pas si je devais aller avec une catégorie ou une sous-classe :-)
kernix

343

Bien que tout ce qui a été dit est correct, cela ne répond pas réellement à votre question de savoir comment supprimer l'avertissement.

Si vous devez avoir ce code pour une raison quelconque (dans mon cas, j'ai HockeyKit dans mon projet et ils remplacent une méthode dans une catégorie UIImage [modifier: ce n'est plus le cas]) et vous devez obtenir votre projet pour compiler , vous pouvez utiliser des #pragmainstructions pour bloquer l'avertissement comme ceci:

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

// do your override

#pragma clang diagnostic pop

J'ai trouvé les informations ici: http://www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html


Merci beaucoup! Donc, je vois que les pragmas peuvent également supprimer les avertissements. :-p
Constantino Tsarouhas

Oui, et bien que ce soient des déclarations spécifiques à LLVM, il y en a également des similaires pour GCC.
Ben Baron

1
L'avertissement dans votre projet de test est un avertissement de l'éditeur de liens, pas un avertissement du compilateur llvm, donc le pragma llvm ne fait rien. Cependant, vous remarquerez que votre projet de test se construit toujours avec "traiter les avertissements comme des erreurs" activé car il s'agit d'un avertissement de l'éditeur de liens.
Ben Baron

12
Cela devrait vraiment être la réponse acceptée, étant donné qu'elle répond réellement à la question.
Rob Jones

1
Cette réponse devrait être la bonne. Quoi qu'il en soit, il a plus de votes que celui sélectionné comme réponse.
Juan Catalan

20

Une meilleure alternative (voir la réponse de bneely à la raison pour laquelle cet avertissement vous sauve du désastre) est d'utiliser la méthode swizzling. En utilisant la méthode swizzling, vous pouvez remplacer une méthode existante à partir d'une catégorie sans l'incertitude de savoir qui "gagne", et tout en préservant la possibilité de faire appel à l'ancienne méthode. Le secret est de donner au remplacement un nom de méthode différent, puis de les échanger à l'aide des fonctions d'exécution.

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

Définissez ensuite votre implémentation personnalisée:

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

Remplacez l'implémentation par défaut par la vôtre:

MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));

10

Essayez ceci dans votre code:

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

UPDATE2: ajouter cette macro

#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
Ce n'est pas un exemple complet. Aucune macro nommée EXCHANGE_METHOD n'est réellement définie par le runtime objective-c.
Richard J.Ross III

@Vitaly toujours -1. cette méthode n'est pas implémentée pour le type de classe. Quel cadre utilisez-vous?
Richard J.Ross III

Désolé encore, essayez ceci j'ai créé le fichier NSObject + MethodExchange
Vitaliy Gervazuk

Avec la catégorie sur NSObject, pourquoi même s'embêter avec la macro? Pourquoi ne pas simplement passer à la «méthode d'échange»?
hvanbrug

5

Vous pouvez utiliser la méthode swizzling pour supprimer cet avertissement du compilateur. Voici comment j'ai implémenté la méthode swizzling pour dessiner des marges dans un UITextField lorsque nous utilisons un arrière-plan personnalisé avec 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

Les propriétés de remplacement sont valides pour une extension de classe (catégorie anonyme), mais pas pour une catégorie standard.

Selon Apple Docs, à l'aide d'une extension de classe (catégorie anonyme), vous pouvez créer une interface privée vers une classe publique, de sorte que l'interface privée puisse remplacer les propriétés exposées publiquement. c'est-à-dire que vous pouvez changer une propriété de readonly à readwrite.

Un cas d'utilisation pour cela est lorsque vous écrivez des bibliothèques qui restreignent l'accès aux propriétés publiques, alors que la même propriété nécessite un accès complet en lecture et en écriture dans la bibliothèque.

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

Recherchez « Utiliser les extensions de classe pour masquer les informations privées ».

Cette technique est donc valable pour une extension de classe, mais pas pour une catégorie.


1

Les catégories sont une bonne chose, mais elles peuvent être abusées. Lorsque vous écrivez des catégories, vous ne devez en principe PAS réimplémenter les méthodes existantes. Cela peut provoquer un effet secondaire étrange car vous réécrivez maintenant du code dont une autre classe dépend. vous pourriez casser une classe connue et finir par retourner votre débogueur. C'est simplement une mauvaise programmation.

Si vous avez besoin de le faire, vous devriez vraiment le sous-classer.

Puis la suggestion de swizzling, c'est un grand NON-NON-NON pour moi.

Le swizzing au moment de l'exécution est un NO-NO-NO complet.

Vous voulez qu'une banane ressemble à une orange, mais seulement à l'exécution? Si vous voulez une orange, écrivez une orange.

Ne faites pas une banane et n'agissez pas comme une orange. Et pire: ne transformez pas votre banane en un agent secret qui sabotera tranquillement les bananes du monde entier pour soutenir les oranges.

Yikes!


3
Swizzing lors de l'exécution peut être utile pour se moquer des comportements dans l'environnement de test, cependant.
Ben G

2
Bien que humoristique, votre réponse ne fait pas vraiment plus que dire que toutes les méthodes possibles sont mauvaises. La nature de la bête est que parfois vous ne pouvez vraiment pas sous-classe, donc vous vous retrouvez avec une catégorie et surtout si vous ne possédez pas le code de la classe que vous catégorisez, parfois vous devez faire le swizzle, et il être une méthode indésirable n'est pas pertinente.
hvanbrug

1

J'ai eu ce problème lorsque j'ai implémenté une méthode déléguée dans une catégorie plutôt que dans la classe principale (même s'il n'y avait pas d'implémentation de classe principale). La solution pour moi était de déplacer le fichier d'en-tête de classe principal vers le fichier d'en-tête de catégorie Cela fonctionne très bien

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.