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

时间:2010-02-03 02:20:43

标签: objective-c pointers

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

9 个答案:

答案 0 :(得分:112)

答案回到了Objective-C的C根源。 Objective-C最初是作为C的编译器预处理器编写的。也就是说,Objective-C的编译不是因为它被转换为直接C然后编译。

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

typedef struct objc_object {
    Class isa;
} *id;

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

@interface NSObject <NSObject> {
    Class   isa;
}

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

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

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

NSRect foo;
NSRect *bar;

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

如果你试着说:

NSArray foo;
NSArray *bar;

编译器会抱怨第一个,在Objective-C 中不允许使用基于堆栈的对象的内容。换句话说,所有 Objective-C对象必须从堆中分配(或多或少 - 有一两个例外,但它们对于这个讨论比较深奥),因此,你总是通过堆上所述对象的地址引用一个对象;你总是使用指向对象的指针(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之前不要打扰。

答案 1 :(得分:15)

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

Objective-C首先在本书中提出并讨论:Object-Oriented Programming: An Evolutionary Approach。对于现代Cocoa程序员来说,它并不是非常实用,但语言的动机就在那里。

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

  

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

     

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

     

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

     

(pp58-59,2nd ed。)

所以你的问题的答案有两个:

  1. 语言设计指定对象的标识符与对象本身不同,标识符是您发送消息的对象,而不是对象本身。
  2. 设计没有规定,但建议我们现在实现,其中指向对象的指针用作标识符。
  3. 严格类型的语法,你说“一个特定于NSString类型的对象”,因此使用NSString *是一个更现代的变化,基本上是一个实现选择,相当于id

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

答案 2 :(得分:10)

因为objc_msgSend()声明如下:

id objc_msgSend(id theReceiver, SEL theSelector, ...)

答案 3 :(得分:9)

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

答案 4 :(得分:6)

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

答案 5 :(得分:4)

部分原因是你会左右指向空指针异常。允许向nil发送消息,并且通常完全合法(它什么都不做,不会产生错误)。

但你可以认为它类似于C ++的->符号:它执行方法并在一段语法糖中取消引用指针。

答案 6 :(得分:4)

我这样说:语言与一系列字母相关联只是一种惯例。设计Objective-C的人决定

[x doSomething];

表示“将doSomething消息发送到 x指向的对象”。他们这样定义,你按照规则:) Objective-C的一个特点,与例如C ++,它没有语法来保存对象本身,而不是指向对象的指针。所以,

NSString* string;

没问题,但是

NSString string;

是非法的。如果后者是可能的,则必须有一种方法“将消息capitalizedString发送到字符串string”,而不是“将消息capitalizedString发送到字符串 string指出。但实际上,您总是向源代码中的变量指向的对象发送消息。

因此,如果Objective-C的设计者遵循了您的逻辑,那么您必须编写

[*x doSomething];

每次发送消息时......您都会看到*需要在前导括号[之后总是,形成组合[*。在那个阶段,我相信您同意重新设计语言更好,这样您只需要通过更改字母序列[的含义来编写[*而不是[x doSomething]

答案 7 :(得分:3)

Objective-C中的对象本质上是structstruct的第一个成员是Class isa(可以使用struct确定isa的总大小。 struct的后续成员可能包含实例变量。

当您声明一个Objective-C对象时,您总是将其声明为指针类型,因为运行时将您的对象提供给其他方法和函数;如果这些更改了struct的任何成员(通过修改实例变量等),它们将“应用”到对象的所有引用,而不仅仅是在方法或函数中的本地引用。

答案 8 :(得分:2)

Objective-C运行时可能需要将对象反弹到几个不同的函数,因此它需要对象引用,而不是对象本身。