我将在这个问题的前言中说明我要问的是仅用于教育和可能的调试目的。
如何在Objective C运行时内部创建块对象?
我看到所有类代表各种块类型的类层次结构,层次结构中{1}}下面的最高超类是NSObject
。转储类数据表明它实现了NSBlock
,+ alloc
,+ allocWithZone:
和+ copy
方法。其他块子类都没有实现这些类方法,这使我相信,或许是错误的,+ copyWithZone:
负责块处理。
但是这些方法似乎在一个块的生命周期中的任何时候都不会被调用。我用自己的实现交换了实现并在每个实例中放置了一个断点,但是它们永远不会被调用。使用NSBlock
的实现进行类似的练习可以提供我想要的内容。
所以我假设块以不同的方式实现?任何人都可以了解这种实施方式的工作原理?即使我无法挂钩分配和复制块,我也想了解内部实现。
答案 0 :(得分:13)
编译器直接将块文字转换为结构和函数。这就是为什么你没有看到alloc
电话。
虽然块是完全成熟的Objective-C对象,但这个事实很少暴露在它们的使用中,使它们成为非常有趣的野兽。
一个第一个怪癖是通常在堆栈上创建块(除非它们是全局块,即没有引用周围上下文的块),然后仅在需要时才在堆上移动。到目前为止,它们是唯一可以在堆栈上分配的Objective-C对象。
可能由于分配中的这种奇怪,语言设计者决定仅通过块文字(即使用^
运算符)来允许块创建。
通过这种方式,编译器可以完全控制块分配。
如clang specification中所述,编译器将自动为它遇到的每个块文字生成两个结构和至少一个函数:
例如对于文字
^ { printf("hello world\n"); }
在32位系统上,编译器将生成以下内容
struct __block_literal_1 {
void *isa;
int flags;
int reserved;
void (*invoke)(struct __block_literal_1 *);
struct __block_descriptor_1 *descriptor;
};
void __block_invoke_1(struct __block_literal_1 *_block) {
printf("hello world\n");
}
static struct __block_descriptor_1 {
unsigned long int reserved;
unsigned long int Block_size;
} __block_descriptor_1 = { 0, sizeof(struct __block_literal_1), __block_invoke_1 };
(顺便说一句,该块有资格作为全局块,因此它将在内存中的固定位置创建)
所以块是Objective-C对象,但是以低级方式:它们只是带有isa
指针的结构。虽然从正式的角度来看它们是NSBlock
的具体子类的实例,但Objective-C API从不用于分配,因此这就是为什么你没有看到alloc
调用:文字是由编译器直接翻译成结构。
答案 1 :(得分:7)
如其他答案中所述,块对象直接在全局存储(由编译器)或在堆栈(由编译的代码)上创建。它们最初不是在堆上创建的。
块对象类似于桥接CoreFoundation对象:Objective-C接口是底层C接口的封面。块对象的-copyWithZone:
方法调用_Block_copy()
函数,但有些代码直接调用_Block_copy()
。这意味着-copyWithZone:
上的断点将无法捕获所有副本。
(是的,您可以在普通C代码中使用块对象。有一个qsort_b()
函数和一个atexit_b()
函数,呃,可能就是它。)
答案 2 :(得分:2)
块基本上是编译魔术。与普通对象不同,它们实际上是直接在堆栈上分配的 - 它们只在复制时放在堆上。
你可以阅读Clang的block implementation specification来了解幕后发生的事情。根据我的理解,简短版本是定义了一个结构类型(表示块及其捕获状态)和一个函数(调用该块),并且对该块的任何引用都被替换为具有结构类型的值。它的调用指针设置为生成的函数,其字段用适当的状态填充。