3

I understand that overriding a method by using a category is a discouraged practice. Nonetheless, I have to deal with some code that does this. When I ran the following code, I was initially surprised that my category method was called in both cases, though thinking about how the linker must have to make a decision at link time for any given symbol made me feel better about it.

My question: Assuming no other categories are in play other than the ones I create, am I guaranteed that the implementation in my category will always be the logic that is called as long as the header for it is imported from somewhere?

someObject.h

#import <Foundation/Foundation.h>

@interface SomeObject : NSObject

- (void)doSomething;

@end

someObject.m

#import "SomeObject.h"

@implementation SomeObject

- (void)doSomething
{
    NSLog(@"Original");
}

@end

someObject+Cat.h

#import <Foundation/Foundation.h>

#import "SomeObject.h"

@interface SomeObject (SomeObject)

- (void)doSomething;

@end

someObject+Cat.m

#import "SomeObject+Cat.h"

@implementation SomeObject (SomeObject)

- (void)doSomething
{
    NSLog(@"New!");
}

@end

someObjectUser.h

#import <Foundation/Foundation.h>

@interface SomeObjectUser : NSObject

- (void)useSomeObject;

@end

someObjectUser.m

#import "SomeObjectUser.h"

#import "SomeObject.h"

@implementation SomeObjectUser

- (void)useSomeObject
{
    [[SomeObject new] doSomething];
}

@end

Test.m

- (void)testExample
{
    [[SomeObject new] doSomething];
    [[SomeObjectUser new] useSomeObject];
}

Result

2013-02-28 11:32:37.417 CategoryExample[933:907] New!
2013-02-28 11:32:37.419 CategoryExample[933:907] New!
jscs
  • 63,694
  • 13
  • 151
  • 195
Ben Flynn
  • 18,524
  • 20
  • 97
  • 142
  • @NikolaiRuhe I disagree, the questions are fundamentally different. – Mike D Feb 28 '13 at 16:58
  • @MikeD The question is a bit different but the answer is valid for both questions. – Sulthan Feb 28 '13 at 17:00
  • I did read that question first and can now see how there is an answer in there, however the fact that the guide says the undefined behavior is "less likely an issue" made me wonder if there were additional insights to be had here... – Ben Flynn Feb 28 '13 at 17:28

2 Answers2

5

Assuming no other categories are in play other than the ones I create, am I guaranteed that the implementation in my category will always be the logic that is called as long as the header for it is imported from somewhere?

I doubt it matters at all whether you import the category header or not. Headers are information for the compiler; categories are added to classes at run time, and the selection of which implementation is used for a given method happens at run time. More importantly...

From Objective-C Programming Guide:

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. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes.

(Emphasis mine.)

Given the use of the word undefined, I would say that the answer to the question is no, there are no guarantees about which implementation will be used when you re-implement an existing method in a category. Practically speaking, if the class is your own, then your category's method will most likely be selected, and I'd trust that to be reliable if it works in testing for a given version of the compiler and runtime.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • Weird! Unless I'm nuts, that is a breaking change from the old behavior, which was that categories always overrode non-category methods, but there was no way to make one category take priority over another. Good catch. – Chuck Feb 28 '13 at 19:52
  • Hey, @Chuck. I don't think anything has really changed -- since category methods are necessarily added to the class after the class is created, I'd fully expect the category implementation to always be used. But the OP asked for a *guarantee*, obviously a strong term, and the docs say *undefined*, so... – Caleb Feb 28 '13 at 20:06
  • Also from the documentation: "A framework-defined method you try to override may itself have been implemented in a category, and so which implementation takes precedence is not defined." If it were not defined in every case, why would they go out of their way to say this? I question the documentation on this... – Ben Flynn Feb 28 '13 at 20:06
  • Also also: "For example, if you override the windowWillClose: delegate method in a category on NSObject, all window delegates in your program then respond using the category method". It sounds like it _is_ defined and the category wins. – Ben Flynn Feb 28 '13 at 20:09
  • @BenFlynn The problem there is that you don't know the order in which categories are added to a class. So if a method that's part of the class's interface is actually implemented inside the framework in a category, you don't know whether your category will be loaded first and then have your method overridden by the framework's category or vice versa. In practice with your own classes, yes, your category implementation will override the implementation in your class, but the *undefined* means that you shouldn't count on that behavior and that it could change and even now may not always work. – Caleb Feb 28 '13 at 20:19
0

UPDATE:

In the SO answer linked below, the link pointing to the Objective-C Category docs is dead. Here is more up to date version. There is a section on avoiding name clashes.


Yes, according this SO answer, (I will post the official Apple link when I find it).

Some warnings, the original method will be inaccessible but you can always through to super, there is no guarantee a method declared in another category (on the same class) will be overriden.

Community
  • 1
  • 1
Mike D
  • 4,938
  • 6
  • 43
  • 99