ObjC - 为什么允许实例访问 .m 文件中的 class 扩展名 属性?

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

众所周知,在 ObjC 中,.h 文件中声明的 属性 是接口 "visible outside",而 .m 文件(class 扩展名)中声明的 属性 可以只能在 .m 中访问,类似于 "private" 或 "hidden"。但实际上可以编译出如下代码

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

事实是,不仅ClassA自己的方法中定义的ClassA *aObj可以访问class扩展属性aIntClassA *aObj 在另一个 _ClassB 中定义,而在同一个 ClassA.m 文件中也可以访问 aInt

据我了解,class 方法 foo 中定义的 aObj 与另一个 class 中定义的任何 ClassA * 类型变量没有区别,并且单独的 .m 文件。但绝不是后者会访问'aInt',说

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*'
}

为什么会这样?这可以用 ObjC 运行时机制或其他东西来解释吗?

与Objective C运行时无关。事实上,如果你使用键值编码,你可以访问 any 属性 and/or 方法从 any class 来自 任何 你想要的源文件,它可以声明为私有或不私有,或者在扩展中或直接声明。这就是某些人(禁止)使用 Apple 私有 API 的方式。

Objective C,像 C 一样,只需要 知道 你的 class 的声明。这是通过导入头文件来完成的。头文件说的是"Look, there is something like ClassA, it has these methods and those properties",然后就可以用了

在 .m 文件中声明的任何内容对其他源文件都是不可见的,因为您通常不会导入 .m 文件(尽管从技术上讲,它可以工作)。尽管如此,声明仍然存在 - 只是编译器在编译其他文件时不知道它。

您可以创建一个虚拟头文件:

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

然后在您的 ClassC:

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

编译 ClassC.m 时,编译器会知道 ClassA 中存在类似 aInt 的内容。链接器 - 作为最后一步 - 然后检查这是否真的是真的,例如如果一个(且只有一个)编译后的源文件包含 aInt.

的定义

试试这个:只需 声明 一个 属性 未在任何地方 定义:

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

然后使用它:

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

编译器会编译代码,但是链接器会说没有 definition of oopsDoesItExist

最后一点:您只能在 .m 文件中的 class 扩展名(匿名类别)中定义 iVars 或综合属性。参见 https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html