首页 > 网站 > 建站经验 > 正文

iOS开发:教你动手实现objc_m,sgSend

2019-11-02 14:14:26
字体:
来源:转载
供稿:网友

   objc_msgSend 函数支撑了我们使用 Objective-C 实现的一切。Gwynne Raskind,Friday Q&A 的读者,建议我谈谈 objc_msgSend 的内部实现。要理解某件事还有比自己动手实现一次更好的方法吗?咱们来自己动手实现一个 objc_msgSend。

  Tramapoline! Trampopoline! (蹦床)

  当你写了一个发送 Objective-C 消息的方法:

  [obj message]

  编译器会生成一个 objc_msgSend 调用:

  objc_msgSend(obj, @selector(message));

  之后 objc_msgSend 会负责转发这个消息。

  它都做了什么?它会查找合适的函数指针或者 IMP,然后调用,最后跳转。任何传给 objc_msgSend 的参数,最终都会成为 IMP 的参数。 IMP 的返回值成为了最开始被调用的方法的返回值。

  因为 objcmsgSend 只是负责接收参数,找到合适的函数指针,然后跳转,有时管这种叫做 trampoline(译注:[蹦床](https://en.wikipedia.org/wiki/Trampoline(computing)). 更通用的来说,任何一段负责把一段代码转发到另一处的代码,都可以被叫做 trampoline。

  这种转发的行为使 objc_msgSend 变得特殊起来。因为它只是简单的查找合适的代码,然后直接跳转过去,这相当的通用。传入任何参数组合都可以,因为它只是把这些参数留给 IMP 去读取。返回值有些棘手,但最终都可以看成 objc_msgSend 的不同变种。

  不幸的是,这些转发行为都不能用纯 C 实现。因为没有方法可以将传入 C 函数的泛参(generic parameters)传给另一个函数。 你可以使用变参,但是变参和普通参数的传递方法不同,而且慢,所以这不适合普通的 C 参数。

  如果要用 C 来实现 objc_msgSend,基本样子应该像这样:

  id objc_msgSend(id self, SEL _cmd, ...)

  {

  Class c = object_getClass(self);

  IMP imp = class_getMethodImplementation(c, _cmd);

  return imp(self, _cmd, ...);

  }

  这有点过于简单。事实上会有一个方法缓存来提升查找速度,像这样:

  id objc_msgSend(id self, SEL _cmd, ...)

  {

  Class c = object_getClass(self);

  IMP imp = cache_lookup(c, _cmd);

  if(!imp)

  imp = class_getMethodImplementation(c, _cmd);

  return imp(self, _cmd, ...);

  }

  通常为了速度,cache_lookup 使用 inline 函数实现。

  汇编

  在 Apple 版的 runtime 中,为了最大化速度,整个函数是使用汇编实现的。在 Objective-C 中每次发送消息都会调用 objc_msgSend,在一个应用中最简单的动作都会有成千或者上百万的消息。

  为了让事情更简单,我自己的实现中会尽可能少的使用汇编,使用独立的 C 函数抽象复杂度。汇编代码会实现下面的功能:

  id objc_msgSend(id self, SEL _cmd, ...)

  {

  IMP imp = GetImplementation(self, _cmd);

  imp(self, _cmd, ...);

  }

  GetImplementation 可以用更可读的方式工作。

  汇编代码需要:

  1. 把所有潜在的参数存储在安全的地方,确保 GetImplementation 不会覆盖它们。

  2. 调用 GetImplementation。

  3. 把返回值保存在某处。

  4. 恢复所有的参数值。

  5. 跳转到 GetImplementation 返回的 IMP。

  让我们开始吧!

  这里我会尝试使用 x86-64 汇编,这样可以很方便的在 Mac 上工作。这些概念也可以应用于 i386 或者 ARM。

  这个函数会保存在独立的文件中,叫做 msgsend-asm.s。这个文件可以像源文件那样传递给编译器,然后会被编译并链接到程序中。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表