为什么而不是?

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

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

从typesid的定义开始。 它被宣布为:

 typedef struct objc_object { Class isa; } *id; 

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

 @interface NSObject <NSObject> { Class isa; } 

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

当你为NSObject创build子类并添加一些实例variables时,为了所有的目的和目的,只需创build一个新的C结构,该结构包含实例variables作为该结构中的插槽连接在所有超类的实例variables的插槽上。 (现代运行时的工作原理略有不同,所以超类可以添加ivars而不需要重新编译所有的子类)。

现在,考虑这两个variables之间的差异:

 NSRect foo; NSRect *bar; 

(NSRect是一个简单的C结构 – 不涉及ObjC)。 foo是使用堆栈上的存储创build的。 一旦堆栈框架closures,它将无法存活,但是您也不必释放任何内存。 bar是对使用malloc()最有可能在堆上创build的NSRect结构的引用。

如果你试图说:

 NSArray foo; NSArray *bar; 

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

回到语言的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() ,第一个参数被声明为idtypes:

 OBJC_EXPORT id objc_msgSend(id self, SEL op, ...); 

这就是为什么你不使用*foo作为方法调用的目标。 通过上述forms进行翻译 – 对[myArray objectAtIndex: 42]的调用被转换为上面的C函数调用,然后必须调用具有相同的C函数调用声明的东西(全部用方法语法装饰)。

对象的引用是通过它传递的,因为它给予了信使 – objc_msgSend()访问类然后find方法的实现 – 以及那个引用,然后成为第一个参数 – 方法的自我 – 最终执行。

如果你真的想深入, 从这里开始 。 但是,除非你已经完全熟悉这一点,否则不要烦恼。

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

Objective-C最早是在本书中提出和讨论的: 面向对象编程:一种演进的方法 。 对于现代cocoa程序员来说,这并不是非常实用,但是语言的动机就在那里。

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

对象标识符必须唯一标识任何时候在系统中共存的对象。 它们存储在本地variables中,作为消息expression式和函数调用的parameter passing,保存在实例variables(对象内部的字段)中,以及其他types的内存结构中。 换句话说,它们可以像基础语言的内置types那样stream畅地使用。

对象标识符如何实际标识对象是一个实现细节,许多select都是合理的。 一个合理的select,当然是Objective-C中最简单的select之一,就是使用内存中对象的物理地址作为其标识符。 Objective-C通过在每个文件中生成一个typedef语句来使这个决定为C所知。 这定义了一个新的types,id,就C已经理解的另一个types而言,即指向结构的指针。 […]

一个id消耗一定数量的空间。 […]这个空间不同于私有数据在对象本身占用的空间。

(第58-59页,第二版)

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

  1. 语言devise指定对象的标识符不同于对象本身,标识符是您发送消息的标识符,而不是对象本身。
  2. 这个devise并没有规定,而是build议我们现在的实现,其中指向对象的指针被用作标识符。

严格types的语法,你说的“一个对象具体的NSStringtypes”,因此使用NSString *是一个更现代的变化,基本上是一个实现的select,相当于id

如果这看起来像是对一个关于指针取消引用的问题的高度回应,那么记住Objective-C中的对象是根据语言的定义是“特殊的”是很重要的。 它们作为结构来实现 ,并作为结构的指针传递,但它们在概念上是不同的。

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

因为objc_msgSend()是这样声明的:

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

你永远不会取消引用对象指针,句点。 事实上,他们被指定为指针而不是“对象types”,这是该语言C遗产的一个产物。 它完全等同于Java的types系统,对象总是通过引用来访问。 你永远不会用Java解引用一个对象 – 事实上,你不能。 你不应该把它们当作指针,因为从语义上讲,它们不是。 他们只是对象引用。

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

但是你可以把它看作类似于C ++的->符号:它执行方法并在一个语法糖中提取指针。

我会这么说:一种语言与一系列字母联系在一起只是一个惯例。 deviseObjective-C的人决定

 [x doSomething]; 

意思是“将doSomething消息发送给x 指向的对象”。 他们是这样定义的,你遵循规则:)与C ++相比,Objective-C的一个特点是它没有一个句法来保存一个对象本身,而不是一个指向对象的指针。 所以,

 NSString* string; 

是的,但是

 NSString string; 

是非法的。 如果后者是可能的,那么必须有一种方法来“将capitalizedStringstring信息发送给string ”,而不是“将信息capitalizedStringstring发送到string指向的 string ”。 但实际上,你总是发送一条消息给你的源代码中的一个variables指向的对象。

所以,如果Objective-C的devise者遵循了你的逻辑,你就必须写

 [*x doSomething]; 

每次你发送一条消息…你会发现, * 总是出现在括号后面,形成组合[* 。 在那个阶段,我相信你们同意重新devise语言是更好的,所以你只需要写[而不是[* ,通过改变字母序列的含义[x doSomething]

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

当你声明一个Objective-C对象时,你总是把它声明为一个指针types,因为运行时会将你的对象赋予其他的方法和函数。 如果这些改变了struct任何成员(通过修改实例variables等),它们将“应用”到你的对象的所有引用,而不仅仅是本地的方法或函数。

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