NSObject's category is available for every NSObject subclass even without any import of this category's .h file anywhere
Asked Answered
D

2

9

Background.

Please consider the following steps:

1) In Xcode create a new "Single View Application".

2) Create a category NSObject+Extension.h and .m files:

// .h
@interface NSObject (Extension)
- (void)someMethod;
@end

// .m
@implementation NSObject (Extension)
- (void)someMethod {
    NSLog(@"someMethod was called");
}
@end

3) Ensure that NSObject+Extension.m file is included into a main target.

4) Add the following line to AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[NSString new] performSelector:@selector(someMethod)];

    return YES;
}

5) Ensure that #import "NSObject+Extension.h line does not exists anywhere in the app!

6) Run Application.

The output is

2013-08-27 04:12:53.642 Experimental[32263:c07] someMethod was called

Questions

  1. I wonder if there is no any #import of this category anywhere in the app, how is it even possible that NSString does still have NSObject+Extension available? This behavior makes me feeling very bad about every Objective-C category I declare because I want the categories I declare to be available only in the scopes they are declared within. For example, I want NSObject to be extended by Extension only in some class but not in the whole app because its globalspace becomes "polluted" otherwise.

  2. Is there a way to avoid this behavior? I do want my categories to work only when I explicitly import them, not when I just have them linked to a target I use to run.

Drumlin answered 27/8, 2013 at 1:30 Comment(0)
K
14

I wonder if there is no any #import of this category anywhere in the app, how is it even possible that NSString does still have NSObject+Extension available? This behavior makes me feeling very bad about every Objective-C category I declare because I want the categories I declare to be available only in the scopes they are declared within. For example, I want NSObject to be extended by Extension only in some class but not in the whole app because its globalspace becomes "polluted" otherwise.

There are no namespaces on Objective-C objects. If you declare that a class has a method (whether via a category or on the primary @interface) then every instance of that class will have that method.

The way that Objective-C deals with "private" methods is by choosing not to tell other people about the methods in question (which is accomplished by not #import-ing the file that declares those methods). This, coupled with -Wundeclared-selector (warn if you use a selector that the compiler doesn't know about) is about as good of a guard as you're going to get.

But regardless, if you compile the .m file into your final binary, the method will exist, even if no one else "knows" about it.

Are there way to avoid this behavior? I do want my categories to work only when I explicitly import them, not just when I have them linked to a target I use to run.

Yeah, use -Wundeclared-selector, and Xcode will warn you.

Kristoferkristoffer answered 27/8, 2013 at 1:35 Comment(0)
I
4

Including the header just makes it so the compiler knows about it. It compiles it regardless because xCode compiles every file included in a target. At runtime, the method will be there, so even if you didn't include it for compile time checking, the object will still respond to that category method.

Irremovable answered 27/8, 2013 at 1:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.