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?
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