在Objective-C中,为什么是[object doSomething]
?既然你在对象上调用一个方法,那不是[*object doSomething]
了吗?,这意味着你应该取消对指针的引用?
在Objective-C中,为什么是[object doSomething]
?既然你在对象上调用一个方法,那不是[*object doSomething]
了吗?,这意味着你应该取消对指针的引用?
答案可以追溯到Objective-C的C根.Objective-C最初是作为C的编译器预处理器编写的.也就是说,Objective-C不是编译的,而是转换成直接的C,然后编译的.
从类型id
的定义开始.声明如下:
typedef struct objc_object {
Class isa;
} *id;
也就是说,id
是指向第一个字段为Class类型的 struct 的指针(其本身是指向定义类的 struct 的指针).现在,考虑NSObject
:
@interface NSObject <NSObject> {
Class isa;
}
请注意,NSObject
的布局和id
所指类型的布局是相同的.这是因为,实际上,Objective-C对象的实例实际上只是指向一个 struct 的指针,该 struct 的第一个字段(始终是指针)指向包含该实例的方法的类(以及其他一些元数据).
当您将NSObject子类化并添加一些实例变量时,出于所有意图和目的,您只需创建一个新的C struct ,其中包含您的实例变量,作为该 struct 中连接到所有超类的实例变量插槽上的插槽.(现代运行时的工作方式有slightly种不同,因此一个超类可以附加IVAR,而无需重新编译所有子类).
现在,考虑这两个变量之间的差异:
NSRect foo;
NSRect *bar;
(NSRect是一个简单的C struct ——不涉及ObjC).foo
与堆栈上的存储一起创建.一旦堆栈框架关闭,它将无法存活,但您也不必释放任何内存.bar
是对NSRect struct 的引用,该 struct 很可能是使用malloc()
在堆上创建的.
如果你想说:
NSArray foo;
NSArray *bar;
编译器会抱怨第一个,说一些与stack based objects are not allowed in Objective-C类似的话.换句话说,必须从堆中分配all个Objective-C对象(或多或少——有一个或两个例外,但它们对本文的讨论相对来说比较深奥),因此,您always通过堆上所述对象的地址引用对象;您总是使用指向对象的指针(id
类型实际上只是指向任何旧对象的指针).
回到语言的C预处理器根,您可以将每个方法调用转换为等效的C行.例如,以下两行代码是相同的:
[myArray objectAtIndex: 42];
objc_msgSend(myArray, @selector(objectAtIndex:), 42);
类似地,一个声明如下的方法:
- (id) objectAtIndex: (NSUInteger) a;
等价于如下声明的C函数:
id object_at_index(id self, SEL _cmd, NSUInteger a);
看objc_msgSend()
,第一个参数被声明为id
类型:
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...);
这就是为什么不使用*foo
作为方法调用的目标.通过上面的形式进行转换——对[myArray objectAtIndex: 42]
的调用被转换为上面的C函数调用,然后它必须使用等价的C函数调用声明(都用方法语法修饰)调用某些东西.
执行对象引用是因为它让messenger——objc_msgSend()访问该类,然后找到方法实现——以及该引用,然后成为最终执行的方法的第一个参数——self.
如果你真的想深入,start here.但是,在你完全掌握grokked this之前不要操心.