Nil

翻译自:http://ridiculousfish.com/blog/posts/nil.html

发送一条消息给nil时候,Obejctive-C是如何处理的?它会返回什么值?你应该知道当一个方法定义了需要返回对象时候,将会获取到nil,但是如果方法定义返回charintlong longfloatstruct类型时候,它是如何处理的?

接下来研究下。

objc_msgSend

这个函数是我们知道的。当一个对象发送一条消息时,例如[someObject doSomething],编译器把这段代码转换成调用objc_msgSend()的函数(或者当方法声明返回struct变量时是调用objc_msgSend_stret(),下面会有详细介绍)。objc_msgSend()是如何处理nil的?

幸运的是这段代码是开源的。我们需要的是objc-msg-ppc.s文件,.s后缀是组装的意思,不用担心这点。

(接下来会在这篇博客中展示那个代码的代码片段。所有代码的版权都是苹果公司的)

objc_msgSend()函数在那边?搜索 ENTRY _objc_msgSend就可以找到。


ENTRY _objc_msgSend
; check whether receiver is nil
cmplwi r3,0 ; receiver nil?
beq- LMsgSendNilSelf ; if so, call handler or return nil

首先分析cmplwi r3,0这行,它是将r3(它是对象)和0作比较。接下来分析 beq- LMsgSendNilSelf ,它是如果r30相等时跳转到LMsgSendNilSelf

LMsgSendNilSelf

LMsgSendNilSelf的实际是什么样子?在上面文件内搜索它就可以找到。它是消息接收者是nil的处理,但是它是如何处理的?


LMsgSendNilSelf:
mflr r0 ; load new receiver
bcl 20,31,1f ; 31 is cr7[so]
1: mflr r11
addis r11,r11,ha16(__objc_nilReceiver-1b)
lwz r11,lo16(__objc_nilReceiver-1b)(r11)
mtlr r0
cmplwi r11,0 ; return nil if no new receiver
beqlr
mr r3,r11 ; send to new receiver
b LMsgSendReceiverOk

LMsgSendNilSelf前六行是加载全局变量__objc_nilReceiver到寄存器r11中(六行仅仅是加载变量。位置独立代码的全局变量不是那么漂亮的)。然后它通过cmplwi r11, 0把变量和nil对比,如果它是nil,则objc_msgSend()返回。如果没有返回值,它会跳转到返回处。

(如果__objc_nilReceiver不为nil,它将会将__objc_nilReceiver移到寄存器r3中,在r3消息继续,意味着__objc_nilReceiver是替换默认消息为nil行为的地方。这样很酷吧!)

What gets returned

总结下,如果发送一条消息给nil,当执行C函数时,它不会返回任何一条指定的返回值。所以什么返回值会返回给我们了?下面假设Mac OS X在PowerPC上怎么返回值。

它的声明函数依赖函数类型!这个东西全被写在《PowerPC Runtime Architecture Guide》,同时在你电脑/Developer某处有同样的文章。真是一团糟。接下来是《Function Return》。下面摘录了部分描述:


1,如果函数返回了一个指针或者整型(而不是long long类型),返回值将跳转到第四通用寄存器r3中。
2,如果函数返回浮点指针类型,返回值将进入一个浮点指针寄存器FPR1。
3,如果函数返回long long类型,上半部分进入寄存器r3,下半部进入寄存器r4。
4,struct返回工作原理如下:被调用者为返回值创建区域,并将一个指针粘贴到寄存器r3的对应区域(因此开始将参数放到寄存器r4而不是寄存器r3),被调用者负责拷贝返回值到那块区域。现在我们可以知道为什么struct返回值需要特殊的objc_msgSend_stret()函数了:所有的参数是一个它们常在的寄存器。

但是稍等,寄存器r3不是我们存放对象发送消息的第一位置?事实如此!当你在声明需要返回对象的函数中发送消息给nil,你可以得到同样为nil的返回值。函数没有接触到一个对象参数,自返回值和objc_msgSend()收到参数的地址是一致的,如果接收者为nil,然后会返回值。

Types, types, types

intshortcharlong类型的返回值也会跳转到寄存器r3,和上面一样的。发送消息给nil会返回给你0

但是long long类型,它的返回值一半在寄存器r3,一半在寄存器r4?但是objc_msgSend()没有接触到寄存器r4,在这个例子中我们发送selector到寄存器r4中。因此,对于long long类型的情况,我们希望得到的前32位的全是0和等于selector发送的底部32位。如果我们发送消息给nil,并期望是long long类型时,我们将永远不会得到返回值为0相反,我们得到可转换为long long类型的selector

浮点指针?objc_msgsend()接触不到浮点寄存器,所以当发送消息时,我们需要会回到任何碰巧在寄存器FPR1中。

好的,现在最棘手的案例是:structobjc_msgsend_stret()objc_msgsend()在处理nil-receiver的方式上一样的。记得被调用者要负责复制的返回值为调用者提供空间。因为objc_msgsend_stret()忽略这样做,由调用者提供的空间数据是不变的。这通常意味着类似myPoint = [nil someMethodReturningPoint];这种代码将导致myPoint(但这种行为是依赖于编译器的优化,所以你不应该依赖于它)。

唷,我的帽子和你一起走过这篇博客。希望你会有新的发现。

本文转载请注明原作者呆萌院长,如果你对这篇文章有更好的见解可以通过微信联系我。利益相关:本篇文章所有涉及到的软件均为笔者日常所用工具,无任何广告费用。

你可能感兴趣的:(Nil)