web-dev-qa-db-fra.com

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

Je me demandais comment supprimer l'avertissement:

Category implémente 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];
}
97
Doz

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 identique à une méthode de la classe d'origine ou à une méthode d'une autre catégorie de la même classe (ou même d'une superclasse), le comportement n'est pas défini quant à l'implémentation de la méthode utilisée à exécution.

Deux méthodes avec exactement la même signature dans la même classe conduiraient à 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.

63
bneely

Bien que tout ce qui a été dit avec bonté soit correct, cela ne répond pas à 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 à compiler , vous pouvez utiliser #pragma instructions 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

341
Ben Baron

Une meilleure alternative (voir la réponse de bneely pour savoir pourquoi cet avertissement vous sauve d'une catastrophe) consiste à utiliser la méthode swizzling. En utilisant la méthode swizzling, vous pouvez remplacer une méthode existante d'une catégorie sans l'incertitude de qui "gagne" et tout en conservant la possibilité d'appeler à l'ancienne méthode. Le secret consiste à donner à la substitution un nom de méthode différent, puis à 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:));
20
Sanjit Saluja

Essayez ceci dans votre code:

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

PDATE2: 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
10
Vitaliy Gervazuk

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
5
Say2Manuj

Les propriétés prioritaires sont valables pour une extension de classe (catégorie anonyme), mais pas pour une catégorie régulière.

Selon Apple Docs utilisant une extension de classe (catégorie anonyme), vous pouvez créer une interface privée vers une classe publique, de telle sorte que l'interface privée puisse remplacer les propriétés exposées publiquement. Par exemple, vous pouvez modifier une propriété de lecture seule à réécriture.

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

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

Recherchez " Utilisez 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.

2
Kris Subramanian

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 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 la classe principale vers le fichier d'en-tête de catégorie.

1
gheese

Les catégories sont une bonne chose, mais elles peuvent être abusées. Lorsque vous écrivez des catégories, vous devez en principe NE PAS réimplémenter les méthodes de sortie. Cela pourrait provoquer des effets secondaires étranges car vous êtes en train de réécrire du code dont dépend une autre classe. vous pourriez casser une classe connue et finir par transformer votre débogueur à l'envers. C'est tout simplement une mauvaise programmation.

Si vous devez le faire, vous devez vraiment le sous-classer.

Ensuite, la suggestion de swizzling, c'est un gros NON-NON-NON pour moi.

Swizzing à l'exécution est un NON-NON-NON complet.

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

Ne faites pas ressembler une banane et agissez 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.

Oui!

1
Leander