为什么而不是?
在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 – 有一两个例外,但是对于这个讨论来说,它们是比较深奥的),因此,你总是通过一个对象在堆上的所述对象的地址; 你总是使用指向对象的指针(而id
types实际上只是指向任何旧对象的指针)。
回到语言的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
types:
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页,第二版)
所以你的问题的答案是双重的:
- 语言devise指定对象的标识符不同于对象本身,标识符是您发送消息的标识符,而不是对象本身。
- 这个devise并没有规定,而是build议我们现在的实现,其中指向对象的指针被用作标识符。
严格types的语法,你说的“一个对象具体的NSStringtypes”,因此使用NSString *
是一个更现代的变化,基本上是一个实现的select,相当于id
。
如果这看起来像是对一个关于指针取消引用的问题的高度回应,那么记住Objective-C中的对象是根据语言的定义是“特殊的”是很重要的。 它们作为结构来实现 ,并作为结构的指针传递,但它们在概念上是不同的。
- 这不是一个指针,它是一个对象的引用。
- 这不是一种方法,这是一个信息。
因为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;
是非法的。 如果后者是可能的,那么必须有一种方法来“将capitalizedString
string信息发送给string
”,而不是“将信息capitalizedString
string发送到string指向的 string
”。 但实际上,你总是发送一条消息给你的源代码中的一个variables指向的对象。
所以,如果Objective-C的devise者遵循了你的逻辑,你就必须写
[*x doSomething];
每次你发送一条消息…你会发现, *
总是出现在括号后面,形成组合[*
。 在那个阶段,我相信你们同意重新devise语言是更好的,所以你只需要写[
而不是[*
,通过改变字母序列的含义[x doSomething]
。
Objective-C中的一个对象本质上是一个struct
。 struct
的第一个成员是Class isa
(结构的总大小可以使用isa
来确定)。 该struct
后续成员可能包含实例variables。
当你声明一个Objective-C对象时,你总是把它声明为一个指针types,因为运行时会将你的对象赋予其他的方法和函数。 如果这些改变了struct
任何成员(通过修改实例variables等),它们将“应用”到你的对象的所有引用,而不仅仅是本地的方法或函数。
Objective-C运行时可能需要将对象reflection到几个不同的函数,所以它需要对象引用,而不是对象本身。