为什么[object doSomething]而不是[*object doSomething]?

chr*_*yal 48 pointers objective-c

在Objective-C中,为什么[object doSomething]?不是[*object doSomething]因为你在对象上调用一个方法吗?这意味着你应该取消引用指针?

bbu*_*bum 113

答案回到了Objective-C的C根源.Objective-C最初是作为C的编译器预处理器编写的.也就是说,Objective-C没有编译得那么多,因为它被转换成直接C然后编译.

从类型的定义开始id.它被声明为:

typedef struct objc_object {
    Class isa;
} *id;
Run Code Online (Sandbox Code Playgroud)

也就是说,a id是指向第一个字段类型为Class的结构的指针(它本身是指向定义类的结构的指针).现在,考虑一下NSObject:

@interface NSObject <NSObject> {
    Class   isa;
}
Run Code Online (Sandbox Code Playgroud)

请注意,NSObject指向的类型的布局和布局id是相同的.这是因为,实际上,Objective-C对象的实例实际上只是指向结构的指针,该结构的第一个字段 - 总是一个指针 - 指向包含该实例的方法的类(以及一些其他元数据) ).

当您继承NSObject并添加一些实例变量时,无论是出于所有意图和目的,只需创建一个新的C结构,其中包含您的实例变量作为该结构中的插槽,并连接在所有超类的实例变量的插槽中.(现代运行时的工作方式略有不同,因此超类可以附加ivars,而不需要重新编译所有子类).

现在,考虑这两个变量之间的区别:

NSRect foo;
NSRect *bar;
Run Code Online (Sandbox Code Playgroud)

(NSRect是一个简单的C结构 - 不涉及ObjC). foo是使用堆栈上的存储创建的.一旦堆栈帧关闭,它将无法生存,但您也不必释放任何内存. bar是对NSRect结构的引用,该结构很可能是使用在堆上创建的malloc().

如果你试着说:

NSArray foo;
NSArray *bar;
Run Code Online (Sandbox Code Playgroud)

编译器会抱怨第一个,在Objective-C中不允许使用基于堆栈的对象.换句话说,所有 Objective-C对象必须从堆中分配(或多或少 - 有一两个例外,但它们对于这个讨论比较深奥),因此,你总是通过一个对象来引用一个对象堆上所述对象的地址; 你总是使用指向对象的指针(而id类型实际上只是指向任何旧对象的指针).

回到语言的C预处理器根,您可以将每个方法调用转换为等效的C行.例如,以下两行代码是相同的:

[myArray objectAtIndex: 42];
objc_msgSend(myArray, @selector(objectAtIndex:), 42);
Run Code Online (Sandbox Code Playgroud)

同样,一个声明如下的方法:

- (id) objectAtIndex: (NSUInteger) a;
Run Code Online (Sandbox Code Playgroud)

相当于C函数声明如下:

id object_at_index(id self, SEL _cmd, NSUInteger a);
Run Code Online (Sandbox Code Playgroud)

并且,看一下objc_msgSend(),第一个参数被声明为类型id:

OBJC_EXPORT id objc_msgSend(id self, SEL op, ...);
Run Code Online (Sandbox Code Playgroud)

这正是您不使用*foo方法调用的目标的原因.通过上面的表单进行[myArray objectAtIndex: 42]转换- 调用转换为上面的C函数调用,然后必须使用等效的C函数调用声明调用某些东西(所有这些都用方法语法编写).

引用对象引用因为它给了messenger - objc_msgSend()访问类然后找到方法实现 - 以及那个引用然后成为第一个参数 - 自我 - 最终的方法执行.

如果你真的想深入,请从这里开始.但是在你完全理解之前不要打扰.


Ben*_*tto 15

你不应该真正将它们看作指针到对象.这是一个历史实现细节,它们是指针,并且你在消息发送语法中使用它们(参见@bbum的答案).实际上,它们只是"对象标识符"(或引用).让我们回顾一下,看看概念的基本原理.

Objective-C首先在本书中提出并讨论:面向对象编程:一种进化方法.对于现代Cocoa程序员来说,它并不是非常实用,但语言的动机就在那里.

请注意,在本书中,所有对象都是类型id.你根本没有看到Object *书中更具体的内容; 当我们谈论"为什么"时,这些只是抽象的漏洞.这是本书的内容:

对象标识符必须唯一地标识在任何时候在系统中共存的对象.它们存储在局部变量中,作为参数传递给消息表达式和函数调用,保存在实例变量(对象内的字段)和其他类型的内存结构中.换句话说,它们可以像基本语言的内置类型一样流畅地使用.

对象标识符如何实际标识对象是一个实现细节,许多选择都是合理的.合理的选择,当然是最简单的选择,也是Objective-C中使用的选择,是使用内存中对象的物理地址作为其标识符.Objective-C通过在每个文件中生成一个typedef语句使C知道这个决定.这根据C已经理解的另一种类型(即指向结构的指针)定义了一个新类型id.[...]

id占用固定的空间量.[...]此空间与对象本身中私有数据占用的空间不同.

(第58-59页,第2版)

所以你的问题的答案是双重的:

  1. 语言设计指定对象的标识符与对象本身不同,标识符是您向其发送消息的对象,而不是对象本身.
  2. 该设计没有规定,但建议我们现在实现,其中指向对象的指针用作标识符.

严格类型的语法,你说"一个特定于NSString类型的对象",因此使用NSString *是一个更现代的变化,基本上是一个实现选择,相当于id.

如果这似乎是对指针解除引用问题的高度回应,请务必记住,根据语言的定义,Objective-C中的对象是"特殊的".它们被实现为结构并作为指向结构的指针传递,但它们在概念上是不同的.


Dar*_*ren 10

因为objc_msgSend()声明如下:

id objc_msgSend(id theReceiver, SEL theSelector, ...)
Run Code Online (Sandbox Code Playgroud)


Mat*_*hen 9

  1. 它不是指针,而是对象的引用.
  2. 这不是一种方法,而是一种信息.

  • @unknown:你错过了第二点.您没有调用方法,而是通过引用发送消息. (7认同)

Chu*_*uck 6

你从不取消引用对象指针,句点.它们被键入为指针而不仅仅是"对象类型"的事实是语言C传承的工件.它完全等同于Java的类型系统,其中始终通过引用访问对象.你永远不会在Java中取消引用一个对象 - 事实上,你不能.您不应该将它们视为指针,因为从语义上讲,它们不是.它们只是对象引用.