2013-11-22 86 views
12

我将在序言中提出这个问题,指出我要问的只是教育和可能的调试目的。如何创建NSBlock对象?

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

我看到所有表示各种块类型的类的层次结构,并且层次结构中最低的超类,NSObject,是NSBlock。对班级数据的转储显示它实施了+ alloc,+ allocWithZone:,+ copy+ copyWithZone:方法。其他块子类都没有实现这些类方法,这导致我相信,可能错误地认为NSBlock负责块处理。

但是这些方法似乎在块的使用期限内的任何时候都不会被调用。我用自己的方式交换了实现,并在每个实现中都放置了一个断点,但他们从来没有被调用。使用NSObject的实现方式进行类似的练习使我确切地知道我想要什么。

所以我假设块以不同的方式实现?任何人都可以阐明这个实现是如何工作的?即使我不能挂钩块的分配和复制,我想了解内部实现。

+0

我不认为这是可能使用'[ NSBlock alloc]'创建一个块,这就是为什么他们不被调用。 –

回答

11

tl; dr

编译器直接将块文字转换为结构和函数。这就是为什么你看不到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从不用于分配,所以这就是为什么你看不到调用:文本被编译器直接转换为结构。

+0

我不相信全局块是在堆上创建的;您链接的文档清楚地显示(紧接在您拔出的代码片段之后)该块将成为堆栈块。 (这是有道理的;如果没有运行时数据,你为什么要把它放在堆上呢?)另外,我不相信它们是唯一的被栈分配的objective-c对象;在64位ABI下,整个对象可能嵌入到指针中;所以他们甚至可以只是注册分配。 :) –

+1

@JesseRusak全局块非常像'NSString'文字。他们生活在一个固定的位置,因为他们是不变的对象。在堆栈中创建它们需要在每次构建堆栈框架时分配它们,但是由于它们没有引用周围的上下文,因此将其优化。这里很好地解释了这个问题:http://www.cocoawithlove.com/2009/10/how-blocks-are-implemented-and.html –

+0

@JesseRusak关于64位关注的问题,虽然它可以完成,没有任何参考资料表明它实际上已经完成。据我所知,所有Objective-C对象都只能在堆上生存,而块的显着例外。当然,如果你有额外的参考,你可以提供:)我会喜欢被证明是错误的:) –

2

块基本上是编译器魔术。与普通对象不同,它们实际上是直接分配在堆栈上的 - 它们只在复制时才放在堆上。

您可以阅读Clang的block implementation specification以获得一个好主意在幕后发生的事情。就我的理解而言,简短版本是定义了一个结构类型(表示块及其捕获状态)和一个函数(用于调用块),并且对该块的任何引用都被替换为具有结构类型的值其调用指针设置为已生成的函数,并将其字段填入适当的状态。

6

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

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

(是的,你可以使用块对象在普通的C代码。有一个qsort_b()功能和atexit_b()功能,呃,这可能是它。)

+0

嗨,所以全球存储不等于堆栈? – Unheilig