如何创建NSBlock对象?

Leo*_*ica 14 objective-c objective-c-runtime objective-c-blocks

我将在这个问题的前言中说明我要问的是教育和可能的调试目的.

如何在Objective C运行时内部创建块对象?

我看到所有类代表各种块类型的类的层次结构,下面的层次结构中最高的超类NSObjectNSBlock.倾销类的数据显示,它实现了+ alloc,+ allocWithZone:,+ copy+ copyWithZone:方法.其他块子类都没有实现这些类方法,这使我相信(可能是错误的NSBlock)负责块处理.

但是这些方法似乎在一个块的生命周期中的任何时候都不会被调用.我用自己的实现交换了实现并在每个实例中放置了一个断点,但是它们永远不会被调用.用NSObject实现做类似的练习给了我我想要的东西.

所以我假设块以不同的方式实现?任何人都可以了解这种实施方式的工作原理?即使我不能挂钩分配和复制块,我想了解内部实现.

Gab*_*lla 13

TL;博士

编译器直接将块文字转换为结构和函数.这就是你没有看到alloc电话的原因.


讨论

虽然块是完全成熟的Objective-C对象,但这个事实很少暴露在它们的使用中,使它们成为非常有趣的野兽.

第一个怪癖是通常在堆栈上创建块(除非它们是全局块,即没有引用周围上下文的块),然后仅在需要时才在堆上移动.到目前为止,它们是唯一可以在堆栈上分配的Objective-C对象.

可能由于其分配中的这种奇怪,语言设计者决定仅通过块文字(即使用^运算符)来允许块创建.通过这种方式,编译器可以完全控制块分配.

正如clang规范中所解释的,编译器将自动为它遇到的每个块文字生成两个结构和至少一个函数:

  • 块文字结构
  • 块描述符结构
  • 块调用功能

例如对于文字

^ { printf("hello world\n"); }
Run Code Online (Sandbox Code Playgroud)

在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 };
Run Code Online (Sandbox Code Playgroud)

(顺便说一句,该块有资格作为全局块,因此它将在内存中的固定位置创建)

所以块是Objective-C对象,但是以低级方式:它们只是带有isa指针的结构.虽然从形式上看它们是具体子类的实例NSBlock,但Objective-C API从不用于分配,因此您没有看到alloc调用:文本被编译器直接转换为结构.

  • 适当的重点是*你*不能在堆外部分配Objective-C对象.编译器和运行时在少数几个地方使用全局存储和堆栈存储,例如块对象,常量字符串对象和标记指针对象.但是*你*不能在没有异国情调的情况下做同样的事情. (2认同)
  • 这些技巧通常以(1)使用alloca()分配内存开始,(2)使用object_setClass()在该内存中设置isa.如果任何代码调用-retain该对象,则需要额外的复杂性. (2认同)

Gre*_*ker 7

如其他答案中所述,块对象直接在全局存储(由编译器)或在堆栈(由编译的代码)上创建.它们最初不是在堆上创建的.

块对象类似于桥接的CoreFoundation对象:Objective-C接口是底层C接口的封面.块对象的-copyWithZone:方法调用该_Block_copy()函数,但有些代码_Block_copy()直接调用.这意味着断点-copyWithZone:将无法捕获所有副本.

(是的,你可以在普通的C代码中使用块对象.有一个qsort_b()函数和一个atexit_b()函数,呃,可能就是它.)