Sta*_*ich 22 objective-c objective-c-blocks automatic-ref-counting
我刚刚偶然发现了以下SO主题:我们为什么要复制块而不是保留?其中包含以下句子:
但是,从iOS 6开始,它们被视为常规对象,因此您无需担心.
我真的很困惑这个断言,这就是为什么我要问:这个断言是否真的暗示Objective-C开发人员不需要
@property (copy) blockProperties 要么
[^(...){...) {} copy]
将块及其内容从堆栈复制到堆中了吗?
我希望我所做的描述很清楚.
请详细说明.
类似的问题
Mat*_*ens 40
ARC将自动复制块.来自clang的Objective-C自动参考计数文档:
除了作为初始化
__strong参数变量或读取__weak变量的一部分完成的保留之外,每当这些语义要求保留块指针类型的值时,它都具有a的效果Block_copy.当优化器看到结果仅用作调用的参数时,可以删除这些副本.
因此,仅用作函数或方法调用的参数的块可能保留堆栈块,但除非ARC保留块,否则它将复制块.这是由发出调用的编译器实现的objc_retainBlock(),其实现是:
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
Run Code Online (Sandbox Code Playgroud)
将块属性声明为具有复制语义仍然是一个好主意,因为实际上会复制分配给强属性的块.Apple也建议这样做:
您应该将copy指定为属性属性,因为需要复制块以跟踪其在原始范围之外的捕获状态.在使用自动引用计数时,您不必担心这一点,因为它会自动发生,但属性属性的最佳做法是显示结果行为.
请注意,由于ARC提供此保留时复制功能,因此它仅依赖于ARC或ARCLite可用性,并且不需要特定的OS版本或OS_OBJECT_USE_OBJC.
编辑:
事实证明,检查"捕获"变量的地址很难解释,并不总是适合确定块是否已被复制到堆中或仍然驻留在堆上.虽然这里给出块的规格BLOCK实现规范,将充分描述的事实,我尝试一种完全不同的方法:
这是官方规范块实施规范的摘要:
块存在代码(如函数)和包含多个数据和,数据和标志和函数指针的结构,以及用于"捕获变量"的可变长度部分.
请注意,此结构是私有的并且实现已定义.
可以在函数范围中定义块,其中该结构在堆栈本地存储器中创建,或者可以在全局或静态范围中定义,其中结构在静态存储中创建.
块可以"导入"其他块引用,其他变量和__block修改的变量.
当块引用其他变量时,它们将被导入:
堆栈本地(自动)变量将通过制作"const副本"来"导入".
甲__block改性变量将通过分配的指针变量封闭在另一结构的地址来导入.
全局变量将被简单引用(不是"导入").
如果将导入变量,则"捕获的变量"存在于可变长度部分中的上述结构中.也就是说,自动变量(位于块外部)的"对应物"在块的结构内具有存储.
由于这个"捕获变量"是只读的,编译器可以应用一些优化:例如,如果我们需要块的副本,我们实际上只需要堆上捕获变量的一个实例.
将在计算块文字表达式时设置捕获变量的值.这也意味着,捕获的变量的存储必须是可写的(即,没有代码部分).
捕获变量的生命周期是函数的生命周期:每个块的调用都需要这些变量的新副本.
当程序离开块的复合语句时,生存在堆栈中的块中的捕获变量将被销毁.
当块被销毁时,生存在堆上的块中的捕获变量将被销毁.
根据Block API,版本3.5:
最初,当创建块文字时,此结构将存在于堆栈中:
在计算块文字表达式时,基于堆栈的结构初始化如下:
声明并初始化静态描述符结构如下:
一个.调用函数指针设置为一个函数,该函数将Block结构作为其第一个参数,将其余参数(如果有)作为Block并执行Block复合语句.
湾 size字段设置为以下Block文字结构的大小.
C.如果Block文本需要copy_helper和dispose_helper函数指针,则将它们设置为各自的辅助函数.
创建堆栈(或全局)块文字数据结构并初始化如下:
一个.isa字段设置为外部_NSConcreteStackBlock的地址,它是libSystem中提供的未初始化内存块,如果是静态或文件级别块文字,则为_NSConcreteGlobalBlock.
湾 除非有导入块的变量需要帮助函数用于程序级Block_copy()和Block_release()操作,否则flags字段设置为零,在这种情况下,设置(1 << 25)标志位.
请注意,这适用于块文字.
根据Objective-C Extensions to Blocks,编译器会将Blocks视为对象.
现在,很难制作证明这些断言的测试代码.因此,使用调试器并在相关函数上设置符号断点似乎更好
_Block_copy_internal 和
malloc (只有在第一个断点被击中后才能启用)
然后运行合适的测试代码(如下面的代码片段)并检查会发生什么:
在下面的代码片段中,我们创建一个Block文字并将其作为参数传递给调用它的函数:
typedef void (^block_t)(void);
void func(block_t block) {
if (block) {
block();
}
}
void foo(int param)
{
int x0 = param;
func(^{
int y0 = x0;
printf("Hello block 1\n");
printf("Address of auto y0: %p\n", &y0);
printf("Address of captured x0: %p\n", &x0);
});
}
Run Code Online (Sandbox Code Playgroud)
输出如下:
Hello block 1
Address of auto y0: 0x7fff5fbff8dc
Address of captured x0: 0x7fff5fbff940
Run Code Online (Sandbox Code Playgroud)
"捕获"变量的地址x0强烈表明它存在于堆栈中.
我们也设置了一个断点_Block_copy_internal- 但是,它不会被击中.这表明Block尚未复制到堆上.可以使用仪器进行另一个证明,它不显示功能分配foo.
现在,如果我们创建并初始化一个块变量,看起来,最初在堆栈上创建的原始块文字的块数据结构将被复制到堆上:
int capture_me = 1;
dispatch_block_t block = ^{ int y = capture_me; };
Run Code Online (Sandbox Code Playgroud)

上面复制了最初在堆栈上创建的块.这可能只是由于ARC和我们在右手边有一个块文字这一事实,而block左边的块变量将被分配块文字 - 这导致一个Block_copy操作.这使得块看起来很像普通的Objective-C对象.
分配跟踪以下类似代码中的仪器
void foo(int param)
{
dispatch_queue_t queue = dispatch_queue_create("queue", 0);
int x0 = param;
dispatch_block_t block = ^{
int y0 = x0;
printf("Hello block 1\n");
printf("Address of auto y0: %p\n", &y0);
printf("Address of captured x0: %p\n", &x0);
};
block();
}
Run Code Online (Sandbox Code Playgroud)
显示,Block将被复制:

当一个块没有捕获任何变量时,它就像一个普通的函数.然后应用优化是有意义的,其中Block_copy操作实际上什么也没做,因为没有什么可以复制.clang通过使这些块成为"全局块"来实现这一点.
发送copy到块变量并将结果分配给另一个块变量时,例如:
dispatch_block_t block = ^{
int y0 = capture_me;
};
dispatch_block_t otherBlock = [block copy];
Run Code Online (Sandbox Code Playgroud)
copy将是一个非常便宜的操作,因为块block已经为块的结构分配了可以共享的存储.因此,copy不需要再次分配存储.
如果我们需要在某些情况下明确复制块,请回答这个问题,例如:
@property (copy) block_t completion
[^{...} copy]
Run Code Online (Sandbox Code Playgroud)
好吧,一旦块被复制,无论何时,然后将被分配目标变量(块变量) - 我们应该始终安全而不显式复制块,因为它已经在堆中.
在块属性的情况下,如果我们简单地怀疑它应该是安全的:
@property dispatch_block_t completion;
Run Code Online (Sandbox Code Playgroud)
然后:
foo.completion = ^{ x = capture_me; ... };
Run Code Online (Sandbox Code Playgroud)
这应该是安全的,因为分配块文字(它存在于堆栈中)的底层块变量_completion,会将块复制到堆上.
尽管如此,我仍然建议使用属性copy- 因为官方文档仍然建议将其作为最佳实践,并且还支持较旧的API,其中Blocks不像普通的Objective-C对象那样,并且没有ARC.
如果需要,现有系统API还将负责复制块:
dispatch_async(queue, ^{int x = capture_me;});
Run Code Online (Sandbox Code Playgroud)
dispatch_async()将为我们制作副本.所以,我们不必担心.
其他情况更微妙:
dispatch_block_t block;
if (condition) {
block = ^{ ... };
}
else {
block = ^{ ... };
}
dispatch_sync(queue, block);
Run Code Online (Sandbox Code Playgroud)
但实际上,这是安全的:块文字将被复制并分配块变量block.
这个例子看起来甚至可怕:
int x0 = param;
NSArray* array = [NSArray arrayWithObject:^{
int y0 = x0;
printf("Hello block 1\n");
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
block_t block = array[0];
block();
});
Run Code Online (Sandbox Code Playgroud)
但看起来,块文字将被正确复制为将参数(块)分配给id方法中的参数(a )的效果,该方法arrayWithObject:将块文字复制到堆上:

此外,NSArray将保留作为方法中的参数传递的对象arrayWithObject:.这会导致另一个调用Block_copy(),因为Block已经在堆上,此调用不会分配存储.
这表明,在执行过程中某处发送给Block文字的"保留"消息arrayWithObject:也确实会复制该块.
甲字面块 -例如,表达式:
^{...}
Run Code Online (Sandbox Code Playgroud)
将在堆栈上创建块结构.
因此,我们实际上需要做一个副本只有在这里我们需要对堆块,当这不会发生"自动"的情况.但是,实际上并没有这种情况.即使在我们将块作为参数传递给一个方法的情况下,该方法也不知道它是块文字并且需要copy第一个(例如:) arrayWithObject:,并且接收器可能只向参数中给出的对象发送保留消息,块将首先复制到堆上.
我们可能明确使用的示例copy是,我们没有为块文件分配块变量或者一个id,因此ARC无法弄清楚它必须复制块对象或必须发送"retain".
在这种情况下,表达式
[^{...} copy];
Run Code Online (Sandbox Code Playgroud)
将产生一个自动释放的块,其结构驻留在堆上.
| 归档时间: |
|
| 查看次数: |
12221 次 |
| 最近记录: |