Suppress warning "Category is implementing a method which will also be implemented by its primary class"
Asked Answered
C

8

101

I was wondering how to suppress the warning:

Category is implementing a method which will also be implemented by its primary class.

I have this for a specific code category:

+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
    return [self aCustomFontOfSize:fontSize];
}
Cavanaugh answered 24/2, 2012 at 1:14 Comment(4)
By method swizzling. Though I wouldn't do that -- perhaps you could make a UIFont subclass that overrides the same method instead, and call super otherwise.Constituency
Your problem isn't the warning. Your problem is that you have the same method name, which is going to lead to problems.Dunderhead
See Overriding methods using categories in Objective-C for reasons why you should not be overriding methods using categories, and for alternative solutions.Yonina
If you people know a more elegant solution to set the application-wide font, I really would like to hear it!Tjon
E
65

A category allows you to add new methods to an existing class. If you want to reimplement a method that already exists in the class, you typically create a subclass instead of a category.

Apple documentation: Customizing existing classes

If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime.

Two methods with the exact same signature in the same class would lead to unpredictable behavior, because each caller cannot specify which implementation they want.

So, you should either use a category and provide method names that are new and unique for the class, or subclass if you want to change the behavior of an existing method in a class.

Exalt answered 24/2, 2012 at 1:15 Comment(7)
i totally agree to those ideas explained above and trying to follow them during development. but still there are could be cases, where override methods in category could be appropriate. for example, cases where multiple inheritance (like in c++) or interfaces (like in c#) could be used. just faced with that in my project and realized that overriding methods in categories are the best choice.Cordon
It can be useful when unit testing some code that has a singleton in it. Ideally, singletons should be injected into code as a protocol, allowing you to switch out the implementation. But if you already have one embedded inside your code, you can add a category of the singleton in your unit test and overide the sharedInstance and the methods you what to control to turn them into dummy objects.Aborning
Thanks @PsychoDad . I updated the link and added a quote from the documentation that's relevant to this post.Exalt
Looks good. Does Apple provide documentation on the behavior of using a category with an existing method name?Eclecticism
The quote refers to this -- behavior is undefined.Exalt
I like to use categories for organizing code. I do this to avoid files that are overly long and to find code more quickly. I'll typically segment off chunks of code as soon as there's enough that can be logically separated under a category (with a representative name). Though not the main purpose of categories, it's incredibly helpful but results in a number of these types of warnings.Jonette
awesome, wasn't sure if I should go with category or subclass :-)Pythian
A
347

Although everything bneely said is correct, it doesn't actually answer your question of how to suppress the warning.

If you have to have this code in for some reason (in my case I have HockeyKit in my project and they override a method in a UIImage category [edit: this is no longer the case]) and you need to get your project to compile, you can use #pragma statements to block the warning like so:

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

// do your override

#pragma clang diagnostic pop

I found the information here: http://www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html

Annulation answered 8/3, 2012 at 18:23 Comment(16)
Thanks a lot! So, I see pragmas can suppress warnings too. :-pConcomitance
Yep, and though these are LLVM specific statements, there are similar ones for GCC as well.Annulation
Do you have any suggestions to why this might not be working in XCode 4.4?Paraffinic
Sorry I haven't used this switch in my code anymore for a while now. They removed the method override from the category in HockeyKit and I don't do overrides in my own categories.Annulation
I just tested and this is still working for me in Xcode 4.4.1. I do get a warning from ld, but the project compiles even though I have treat warnings as errors turned on (as I'm assuming that refers only to compiler warnings)Annulation
@JamesWebster I don't know if this is your problem, but wherever I originally copied this from, the warning had “smart quotes” around it, and it didn't work. Switching them to regular quotes made it work correctly.Hey
It turns out my problem was due to sub-projects in a workspace, removing this pragma produced two versions of the warning. I never did solve it though.Paraffinic
This doesn't seem to work any more. See demo project: github.com/lmirosevic/objc-cat-overrideGauge
The warning in your test project is a linker warning, not a llvm compiler warning, hence the llvm pragma isn't doing anything. However, you'll notice that your test project still builds with "treat warnings as errors" turned on because it's a linker warning.Annulation
This really should be the accepted answer, given that it actually answers the question.Oleary
So is there a defined behavior from using a category to change a method like this?Eclecticism
I believe technically the behavior is undefined. However, in practice, if you override a method in a category, it will use the category implementation, but if it's overridden more than once in different categories, it may use any of those implementations (or perhaps just the last loaded implementation). So in general because it's not defined behavior, it's bad practice to use.Annulation
this is very useful for tests where static methods need to be overwrittenConcede
Looks like a good general solution but, in my case, I'm overriding init, which causes not a compiler warning but a linker warning. Got any tips on how to suppress that one? "instance method 'init' in category from ...blah/blah/CCNode+MyExtraStuff.o overrides method from class in ...blah/blah/CCNode.o"Hone
@Hone take a look at this SO post: #11830012Annulation
This answer should be the correct one. Anyway it has more votes than the one selected as answer.Gisela
E
65

A category allows you to add new methods to an existing class. If you want to reimplement a method that already exists in the class, you typically create a subclass instead of a category.

Apple documentation: Customizing existing classes

If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime.

Two methods with the exact same signature in the same class would lead to unpredictable behavior, because each caller cannot specify which implementation they want.

So, you should either use a category and provide method names that are new and unique for the class, or subclass if you want to change the behavior of an existing method in a class.

Exalt answered 24/2, 2012 at 1:15 Comment(7)
i totally agree to those ideas explained above and trying to follow them during development. but still there are could be cases, where override methods in category could be appropriate. for example, cases where multiple inheritance (like in c++) or interfaces (like in c#) could be used. just faced with that in my project and realized that overriding methods in categories are the best choice.Cordon
It can be useful when unit testing some code that has a singleton in it. Ideally, singletons should be injected into code as a protocol, allowing you to switch out the implementation. But if you already have one embedded inside your code, you can add a category of the singleton in your unit test and overide the sharedInstance and the methods you what to control to turn them into dummy objects.Aborning
Thanks @PsychoDad . I updated the link and added a quote from the documentation that's relevant to this post.Exalt
Looks good. Does Apple provide documentation on the behavior of using a category with an existing method name?Eclecticism
The quote refers to this -- behavior is undefined.Exalt
I like to use categories for organizing code. I do this to avoid files that are overly long and to find code more quickly. I'll typically segment off chunks of code as soon as there's enough that can be logically separated under a category (with a representative name). Though not the main purpose of categories, it's incredibly helpful but results in a number of these types of warnings.Jonette
awesome, wasn't sure if I should go with category or subclass :-)Pythian
C
20

A better alternative (see bneely's answer to why this warning is saving you from disaster) is to use method swizzling. By using method swizzling, you can replace an existing method from a category without the uncertainty of who "wins", and while preserving the ability to call through to the old method. The secret is to give the override a different method name, then swap them using runtime functions.

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

Then define your custom implementation:

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

Override the default implementation with yours:

MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));
Chemisette answered 24/9, 2012 at 20:33 Comment(0)
T
10

Try this in your code:

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

UPDATE2: Add this 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
Tinytinya answered 13/8, 2012 at 10:57 Comment(4)
This isn't a complete example. No macro named EXCHANGE_METHOD is actually defined by the objective-c runtime.Hippy
@Vitaly stil -1. that method is not implemented for the class type. What framework are you using?Hippy
Sorry again, try this out i created file NSObject+MethodExchangeTinytinya
With the category on NSObject why even bother with the macro? Why not just swizzle with the 'exchangeMethod'?Akerboom
E
5

You can use method swizzling to suppress this compiler warning. Here is how I implemented method swizzling for drawing margins in a UITextField when we use a custom background with 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
Eudemonics answered 22/2, 2013 at 9:1 Comment(0)
B
2

Over-riding properties is valid for a Class Extension (Anonymous Category), but not for a regular Category.

According to Apple Docs using a Class Extension (Anonymous Category) you can create a Private interface to a public class, such that the private interface can override the publicly exposed properties. i.e. you can change a property from readonly to readwrite.

A use case for this is when you write libraries that restrict access to public properties, while the same property needs full read write access within the library.

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

Search for "Use Class Extensions to Hide Private Information".

So this technique is valid for a Class Extension, but not for a Category.

Bevel answered 23/12, 2014 at 20:56 Comment(0)
I
1

I had this problem when I implemented a delegate method in a category rather that the main class ( even though there was no main class implementation). The solution for me was to move the from the main class header file to the category header file This works fine

Intervene answered 28/11, 2012 at 9:47 Comment(0)
C
0

Categories are a good thing, but they can be abused. When writing categories your should as a principle NOT re-implement exiting methods. Doing so may cause strange side effect as you are now re-writing code that another class depends one. you could break a known class, and end up turning your debugger inside-out. It is simply bad programming.

If you need todo it, you really should subclass it.

Then the suggestion of swizzling, that is a big NO-NO-NO to me.

Swizzing it at runtime is a complete NO-NO-NO.

You want a banana to look like an orange, but only at runtime? If you want an orange, then write an orange.

Don not make a banana look and act like an orange. And worse: dont turn your banana into a secret agent who will quietly sabotaging bananas worldwide in support of oranges.

Yikes!

Canthus answered 6/11, 2012 at 9:39 Comment(2)
Swizzing at runtime may be useful for mocking behaviors in testing environment, though.Convergent
Although humorous, your answer doesn't really do more than say that all possible methods are bad. The nature of the beast is that sometimes you really can't subclass, so you are left with a category and especially if you don't own the code for the class that you are categorizing, sometimes you need to do the swizzle, and it being an undesirable method is not relevant.Akerboom

© 2022 - 2024 — McMap. All rights reserved.