【问题标题】:Blocks and messaging块和消息传递
【发布时间】:2014-04-04 20:53:26
【问题描述】:

这里的问题更像是一个教育问题。一小时前我开始想到这个 翻转乐高积木(我知道这很傻)。

据我所知,块是在堆栈上创建的对象。

假设A 是一个对象,这意味着我们可以这样做:

[A message];

基于此,如果一个块是一个对象,我们也可以这样做:

[block message];

我说的对吗?

当运行时看到它时,它会调用:

objc_msgSend(block, @selector(message), nil);

所以我的问题是,我们如何发送阻止消息?

如果可能的话,我想也可以发送一个带有块的参数的块消息?

而且,如果我们可以通过以下方式调用块:

block();

这是否意味着我们甚至可以将块作为消息 (SEL),因为块具有类似于方法的签名 void (^)(void)

因为如果有可能,那么下面的内容真的会让我感到惊讶:

objc_msgSend(block, @selector(block), block);

或:

objc_msgSend(block1, @selector(block2), block3);

我希望我的想象力不会有点疯狂,我的理解不会在这里(请纠正我,如果是的话)。

【问题讨论】:

  • 块文字有一个invoke() 函数指针。他们不需要objective-c 接口来调用。这将需要为每个具有不同参数布局的块文字创建一个唯一的 NSBlock 子类(块不仅是void (^)(void)),只是为了一个类型良好的调用方法。
  • 这样,编译器可以完全控制必须生成的任何函数,并且在使用文字时,只需在堆栈上创建块并分配其函数指针。
  • @CodaFi 抱歉,您能否告诉我更多信息并告诉我如何通过更详细(即样本、推理等)完成所要求的内容,因为我仍然有点迷茫.谢谢。
  • @CodaFi 我并不是要以“坏”的方式做事,但对我来说,学习如何以非常规的方式做事最终会成为一种有益的教育。
  • 您可以通过imp_implementationWithBlock()将块用作方法IMP。请参阅下面的答案。

标签: ios objective-c macos objective-c-blocks objective-c-runtime


【解决方案1】:

块是仅用于存储和引用目的的对象。通过使它们成为对象,块可以被保留/释放,因此可以被推入数组或其他集合类中。他们还回复copy

就是这样。即使一个块从堆栈开始,很大程度上也是编译器实现的细节。

当调用块的代码时,这不是通过objc_msgSend() 完成的。如果您要阅读块运行时和 llvm 编译器的源代码,您会发现:

    1234563块的可执行部分
  • 块函数是标准 C 函数,其中第一个参数必须始终是对块的引用。参数列表的其余部分是任意的,就像任何旧的 C 函数或 Objective-C 方法一样工作。

因此,您对 objc_msgSend() 的手动调用将块视为任何其他随机 ObjC 对象,因此,不会调用块中的代码,如果确实如此(并且有 SPI 可以从一个方法......但是,不要使用它)它是否可以传递一个完全可控的参数列表。


除此之外,尽管相关。

imp_implementationWithBlock() 接受一个块引用并返回一个 IMP,该 IMP 可以插入到 Objective-C 类中(请参阅class_addMethod()),以便在调用该方法时调用该块。

imp_implementationWithBlock() 的实现利用了objective-c 方法与块的调用站点的布局。块总是:

blockFunc(blockRef, ...)

而且 ObjC 方法总是:

methodFunc(selfRef, SEL, ...)

因为我们希望 imp_implementationWithBlock() 块始终将目标对象作为第一个块参数(即 self)传递给方法,所以 imp_implementationWithBlock() 在调用时返回一个蹦床函数(通过 objc_msgSend()):

- slides the self reference into the slot for the selector (i.e. arg 0 -> arg 1)
- finds the implementing block puts the pointer to that block into arg 0
- JMPs to the block's implementation pointer

finds the implementation block 有点有趣,但与这个问题无关(见鬼,imp_implementationWithBlock() 也有点无关紧要,但可能很有趣)。


感谢您的回复。这绝对是大开眼界。关于的部分 块调用未通过 objc_msgSend() 完成告诉我它是 因为块不是普通对象层次结构的一部分(而是尾声 提到 NSBlock 似乎反驳了我迄今为止所理解的, 因为 NSBlock 会使它成为对象层次结构的一部分)。随意 如果到目前为止我的理解还不够,那就刺伤我。我是 非常有兴趣了解以下内容 1:SPI 和 调用该方法的方式(如何)。 2:底层机制: 将自参考滑入插槽。 3:找到实现 块并将指向该块的指针放入 arg 0。如果你有时间 分享和写更多关于这些的细节,我全神贯注;一世 发现这一切都非常迷人。提前非常感谢。

这些块本身就是一个标准的 Objective-C 对象。块实例包含指向某些可执行代码的指针、任何捕获的状态,以及一些用于将所述状态从堆栈复制到堆(如果需要)并在块销毁时清理状态的帮助器。

块的可执行代码不像方法那样被调用。块还有其他方法——retainreleasecopy 等等——可以像任何其他方法一样直接调用,但可执行代码不是公开的这些方法之一。

SPI 没有做任何特别的事情;它只适用于不带参数的块,它只不过是简单地做block()

如果您想知道整个参数幻灯片的工作原理(以及它如何使尾部调用通过块),我建议阅读thisthis。同样,block 运行时、objc 运行时和 llvm 的源都可用。

这包括 IMP 抓住块并将其推入 arg0 的有趣部分。

【讨论】:

  • 感谢您的回复。这绝对是大开眼界。关于块调用的部分不是通过objc_msgSend() 完成的,告诉我这是因为块不是正常对象层次结构的一部分(但尾声提到NSBlock 似乎反驳了我到目前为止的理解,因为NSBlock 会使其成为对象层次结构的一部分)。如果到目前为止我的理解仍然不正确,请随意攻击我。我非常有兴趣了解以下内容:
  • (续)1: SPI 以及调用该方法的方式(如何)。 2: 底层机制:将自引用滑入槽中。 3: 找到实现块并将指向该块的指针放入 arg 0。如果您有时间分享并写更多关于这些的详细信息,我会全力以赴;我觉得这一切都非常迷人。非常感谢。
  • 刚刚意识到您是 Playstation 无酸痛自动点火系统的发明者...
  • 感谢您的更新。一定会读起来的。最后一个问题:所以如果不首先将块分配给块变量并随后调用:block();?换句话说,它绝对没有办法在不需要做的情况下“触发”自己:block();?谢谢。
  • @Unheilig 你总是可以做类似^(int f){ ...; }(3); 的事情,但这很奇怪。或者,更一般地说,任何 C 赋值的右侧表达式 (RHS) 都可以在使用变量的任何地方使用(执行顺序更改暗示了明显的副作用)。赋值并没有什么神奇之处(除了自动内存管理之外,C 的行为也很重要)。
【解决方案2】:

是的,块是对象。是的,这意味着您可以向他们发送消息。

但是您认为区块会响应什么信息?除了内存管理消息retainreleasecopy,我们没有被告知任何块支持的消息。因此,如果您向块发送任意消息,它很可能会抛出“不识别选择器”异常(如果您向任何不知道其接口的对象发送任意消息会发生同样的事情) .

调用块是通过与消息发送不同的机制发生的,它是由编译器实现的,不会暴露给程序员。

块和选择器是非常不同的。 (选择器只是一个内部字符串,方法名称的字符串。)块和 IMP(实现方法的函数)有些相似。但是,它们仍然不同,因为方法接收接收器(self)和调用选择器作为特殊参数,而块没有它们(实现块的函数仅接收块本身作为隐藏参数,无法访问程序员)。

【讨论】:

  • 关于 “不识别选择器” 的事情告诉我,发送块消息是“合法的”——只是它不识别 @987654325 @。有没有办法让这成为可能?你能告诉我更多关于“函数实现方法”和“实现块的函数只接收块本身作为隐藏参数”的信息吗?但是正如您所说,程序员无法访问它,但至少在概念上?所以,当一个块被调用时,运行时不会使用objc_msgSend,因为我错误地认为它会......?谢谢。
  • @Unheilig:“运行时不会使用 objc_msgSend,因为我错误地认为它会......?”好吧,程序员不知道它在下面使用什么。但我们知道的一件事是传递给nil 的消息不会崩溃,但调用nil 块会崩溃。 “您能告诉我更多关于“函数实现方法”部分的信息吗?”您可以使用methodForSelector: 获取指向实现方法(IMP)的函数的指针。它是一个 C 函数,开头有两个隐式参数 self_cmd,然后是“常规”参数。
  • @Unheilig:实现block的函数不可访问,但是可以通过runtime函数imp_implementationWithBlock()将具有一定签名的block转换为IMP。
  • 再次感谢@newacct。我花了一些时间来完成这两个答案。 bbum 谈到了我最感兴趣的部分。
猜你喜欢
  • 2018-04-10
  • 2018-06-05
  • 1970-01-01
  • 1970-01-01
  • 2012-12-12
  • 1970-01-01
  • 2012-06-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多