块和消息

Unh*_*lig 3 macos objective-c objective-c-runtime ios objective-c-blocks

这里的问题更多的是教育问题.我在一小时前开始想到这一点,同时翻转一个乐高积木(傻,我知道).

根据我的理解,块是在堆栈上创建的对象.

让我们说A是一个对象,这意味着我们可以做到:

[A message];
Run Code Online (Sandbox Code Playgroud)

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

[block message];
Run Code Online (Sandbox Code Playgroud)

我对么?

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

objc_msgSend(block, @selector(message), nil);
Run Code Online (Sandbox Code Playgroud)

所以我的问题是,我们如何发送一个消息块?

如果这是可能的,我会想象也可以发送一个块参数为块的消息?

并且,如果我们可以通过执行以下操作来调用块:

block();
Run Code Online (Sandbox Code Playgroud)

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

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

objc_msgSend(block, @selector(block), block);
Run Code Online (Sandbox Code Playgroud)

要么:

objc_msgSend(block1, @selector(block2), block3);
Run Code Online (Sandbox Code Playgroud)

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

bbu*_*bum 10

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

就是这样.即使在堆栈上启动的块也主要是编译器实现细节.

调用块的代码时,不会通过objc_msgSend().如果您要阅读块运行时和llvm编译器的源代码,那么您会发现:

  • 块实际上是一个C结构,它包含已捕获数据的描述(因此可以清除)和指向函数的指针 - 一块代码 - 这是块的可执行部分

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

所以,你的手动调用objc_msgSend()像任何其他随机ObjC对象一样处理块,因此,不会调用块中的代码,也不会调用它(如果有的话)(并且SPI可以通过方法执行此操作...但是,不要使用它可以传递一个完全可控的参数列表.


一方面,虽然相关.

imp_implementationWithBlock()获取一个块引用并返回一个IMP,该IMP可以插入到Objective-C类中(请参阅参考资料class_addMethod()),以便在调用该方法时调用该块.

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

blockFunc(blockRef, ...)
Run Code Online (Sandbox Code Playgroud)

ObjC方法总是:

methodFunc(selfRef, SEL, ...)
Run Code Online (Sandbox Code Playgroud)

因为我们希望imp_implementationWithBlock()块始终将目标对象作为第一个块参数(即self)作为方法,imp_implementationWithBlock()返回一个trampoline函数,该函数在调用时(通过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
Run Code Online (Sandbox Code Playgroud)

发现执行块位还挺有趣的,但无关紧要这个问题(地狱,在imp_implementationWithBlock()是有点不相干,太多,但可能有兴趣).


谢谢你的回复.这绝对是一个大开眼界.关于块调用的部分没有通过objc_msgSend()告诉我,这是因为块不是普通对象层次结构的一部分(但是coda提到的NSBlock似乎反驳了我到目前为止所理解的内容,因为NSBlock会使它成为对象层次).如果到目前为止我的理解还没有结束,请随意捅我.我非常有兴趣了解有关以下内容的更多信息:SPI以及调用该方法的方法(如何).2:基本机制:将自引用滑入插槽.3:找到实现块并将指向该块的指针放入arg 0.如果你有时间分享并详细阅读那些详细信息,我全都耳朵; 我发现这一切都非常吸引人.首先十分感谢.

这些块本身就是一个标准的Objective-C对象.块实例包含指向某些可执行代码的指针,任何捕获的状态,以及用于将所述状态从堆栈复制到堆(如果请求)并清除块破坏状态的一些帮助程序.

块的可执行代码不像方法那样被调用.A嵌段的其它方法- ,,retain 等... -可以直接被调用像任何其他方法,但可执行代码是不公开的那些方法之一.releasecopy

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

如果你想知道整个参数幻灯片是如何工作的(以及它如何使尾部调用到块),我建议阅读这个或者这个.同样,块运行时,objc运行时和llvm的源都可用.

这包括IMP抓住块并将其推入arg0的有趣位.