iosobjective-cpropertiesclass-extensions

ObjC - Why is instance allowed to access class extension property in .m file?


As is known that in ObjC property declared in .h file is the interface "visible outside", while property declared in .m file (class extension) can be only accessed in .m, kind of "private" or "hidden". But in practice code as follows can be compiled.

ClassA.h

@interface ClassA : NSObject
+ (void)foo;
@end

ClassA.m

#import "ClassA.h"

@interface ClassA ()
@property (nonatomic) NSInteger aInt;
@end

@implementation ClassA 
+ (void)foo {
    ClassA *aObj = [ClassA new];
    aObj.aInt = 2;  //?
}
@end

@interface _ClassB : NSObject  //Some private class defined in the same .m file...
@end
@implementation _ClassB

+ (void)bar {
    ClassA* aObj = [ClassA new];
    aObj.aInt = 2;  //?
}

@end

The fact is, not only ClassA *aObj defined in ClassA's own method can access class extension property aInt, but ClassA *aObj defined in another _ClassB while in the same ClassA.m file can also access aInt.

As far as I understand, aObj defined in class method foo has no difference with any ClassA * type variable defined in another class and separate .m file. But by no means will the latter access 'aInt', say

ClassC.m

#import "ClassA.h"
...
- (void)fun {
   ClassA *aObj = [ClassA new];
   NSLog("%d", aObj.aInt);  //Error! Property aInt not found on object of type 'ClassA*'
}

Why is this happening? Can this be explained with ObjC runtime mechanism or something?


Solution

  • It has nothing to do with the Objective C runtime. In fact, if you use key value coding, you can access any property and/or method from any class from any source file you want, may it be declared private or not, or in an extension or directly. This is how some people (forbiddenly) use private APIs of Apple.

    Objective C, like C, just needs to know the declarations of your class. This is done by importing the header files. The header file says "Look, there is something like ClassA, it has these methods and those properties", and then you can use them.

    Anything that is declared in a .m file is not visible to other source files, because you typically do not import .m files (although, technically, it works). Nevertheless, the declaration still exist - just the compiler does not know of it when compiling the other file.

    You could create a dummy header file:

    // FakeClassAExtension.h
    // ...
    @interface ClassA (Fake)
    @property (nonatomic) NSInteger aInt;
    @end
    

    and then use it in your ClassC:

    // ClassC.m
    #import "ClassA.h"
    #import "FakeClassAExtension.h"
    //...
    - (void)fun {
        ClassA *aObj = [ClassA new];
        NSLog("%d", aObj.aInt);  //Fine
    }
    

    When compiling ClassC.m, the compiler get's to know that someting like aInt exists in ClassA. The linker - as the final step - then checks if this really is true, e.g. if one (and only one) of the compiled source files contained the definition of aInt.

    Try this: Just declare a property that is not defined anywhere:

    // FakeClassAExtension2.h
    // ...
    @interface ClassA (Fake2)
    @property (nonatomic) NSInteger oopsDoesItExist;
    @end
    

    and then use it:

    // ClassC.m
    #import "ClassA.h"
    #import "FakeClassAExtension2.h"
    //...
    - (void)fun {
        ClassA *aObj = [ClassA new];
        NSLog("%d", aObj.oopsDoesItExist);  //Compiler is fine here
    }
    

    The compiler will compile the code, but the linker then will say that there is no definition of oopsDoesItExist

    Just a final remark: You can define iVars or synthesize properties only in class extensions (anonymous categories) within an .m file. See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html