了解objc中块内存管理的一个边缘情况 [英] Understand one edge case of block memory management in objc

查看:112
本文介绍了了解objc中块内存管理的一个边缘情况的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于 EXC_BAD_ACCESS

typedef void(^myBlock)(void);

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *tmp = [self getBlockArray];
    myBlock block = tmp[0];
    block();
}

- (id)getBlockArray {
    int val = 10;
//crash version
    return [[NSArray alloc] initWithObjects:
            ^{NSLog(@"blk0:%d", val);},
            ^{NSLog(@"blk1:%d", val);}, nil];
//won't crash version
//    return @[^{NSLog(@"block0: %d", val);}, ^{NSLog(@"block1: %d", val);}];
}

代码在启用了ARC的iOS 9中运行。我试图找出导致崩溃的原因。

the code runs in iOS 9 with ARC enabled. And I was trying to figure out the reason that lead to crash.

通过 po tmp 在lldb中我找到了

by po tmp in lldb I found

(lldb) po tmp
<__NSArrayI 0x7fa0f1546330>(
<__NSMallocBlock__: 0x7fa0f15a0fd0>,
<__NSStackBlock__: 0x7fff524e2b60>
)

而在不会崩溃的版本中

(lldb) po tmp
<__NSArrayI 0x7f9db481e6a0>(
<__NSMallocBlock__: 0x7f9db27e09a0>,
<__NSMallocBlock__: 0x7f9db2718f50>
)

因此,我想出的最可能的原因是ARC发布 NSStackBlock 崩溃发生了。但是为什么会这样呢?

So the most possible reason I could come up with is when ARC release the NSStackBlock the crash happen. But why would so?

推荐答案

首先,您需要了解如果要将块存储在声明范围之外,你需要复制它并存储副本。

First, you need to understand that if you want to store a block past the scope where it's declared, you need to copy it and store the copy instead.

这是因为优化,其中捕获变量的块最初位于堆栈上,而不是动态像普通对象一样分配。 (让我们忽略那些暂时不捕获变量的块,因为它们可以作为全局实例实现。)所以当你写一个块文字时,比如 foo = ^ {...}; ,这实际上就像分配给 foo 一个指向同一范围内声明的隐藏局部变量的指针,类似于 some_block_object_t hiddenVariable; foo =& hiddenVariable; 在同步使用块的情况下,此优化减少了对象分配的数量,并且永远不会超出创建它的范围。

The reason for this because of an optimization where blocks which capture variables are initially located on the stack, rather than dynamically allocated like a regular object. (Let's ignore blocks which don't capture variables for the moment, since they can be implemented as a global instance.) So when you write a block literal, like foo = ^{ ...};, that's effectively like assigning to foo a pointer to a hidden local variable declared in that same scope, something like some_block_object_t hiddenVariable; foo = &hiddenVariable; This optimization reduces the number of object allocations in the many cases where a block is used synchronously and never outlives the scope where it was created.

就像指向局部变量的指针一样,如果你将指针指向它所指向的东西的范围之外,你有一个悬空指针,并且取消引用它会导致未定义的行为。如果需要,在块上执行复制会将堆栈移动到堆,其中它像所有其他Objective-C对象一样进行内存管理,并返回指向堆副本的指针(如果块已经是堆块或全局块) ,它只返回相同的指针)。

Like a pointer to a local variable, if you bring the pointer outside the scope of the thing it pointed to, you have a dangling pointer, and dereferencing it leads to undefined behavior. Performing a copy on a block moves a stack to the heap if necessary, where it is memory-managed like all other Objective-C objects, and returns a pointer to the heap copy (and if the block is already a heap block or global block, it simply returns the same pointer).

特定编译器是否在特定情况下使用此优化是一个实现细节,但你不能假设它是如何的已实现,因此如果将块指针存储在比当前作用域更长的位置(例如,在实例或全局变量中,或者在可能超出作用域的数据结构中),则必须始终复制。即使您知道它是如何实现的,并且知道在特定情况下不需要复制(例如,它是一个不捕获变量的块,或者必须已经完成复制),您不应该依赖它,并且当你将它存储在一个比当前范围更长的地方时,你应该总是复制,这是一种好的做法。

Whether the particular compiler uses this optimization or not in a particular circumstance is an implementation detail, but you cannot assume anything about how it's implemented, so you must always copy if you store a block pointer in a place that will outlive the current scope (e.g. in a instance or global variable, or in a data structure that may outlive the scope). Even if you knew how it was implemented, and know that in a particular case copying is not necessary (e.g. it is a block that doesn't capture variables, or copying must already have been done), you should not rely on that, and you should still always copy when you store it in a place that will outlive the current scope, as good practice.

将一个块作为参数传递给一个函数或方法是有点复杂。如果将块指针作为参数传递给声明的编译时类型是块指针类型的函数参数,那么如果该函数的范围超出其范围,则该函数将负责复制它。因此,在这种情况下,您无需担心复制它,而无需知道函数的作用。

Passing a block as an argument to a function or method is somewhat complicated. If you pass a block pointer as an argument to a function parameter whose declared compile-time type is a block-pointer type, then that function would in turn be responsible for copying it if it were to outlive its scope. So in this case, you wouldn't need to worry about copying it, without needing to know what the function did.

另一方面,如果您通过了块指针作为函数参数的参数,其声明的编译时类型是非块对象指针类型,那么该函数不会对任何块复制负责,因为对于所有它知道它只是一个常规对象,如果存储在比当前范围更长的地方,则需要保留。在这种情况下,如果您认为该函数可能存储超出调用结束的值,则应在传递之前复制该块,然后传递该副本。

If, on the other hand, you pass a block pointer as an argument to a function parameter whose declared compile-time type is a non-block object pointer type, then that function wouldn't be taking responsibility for any block copying, because for all it knows it's just a regular object, that just needs to be retained if stored in a place that outlives the current scope. In this case, if you think that the function may possibly store the value beyond the end of the call, you should copy the block before passing it, and pass the copy instead.

顺便说一下,对于任何其他情况,分配块指针类型或转换为常规对象指针类型也是如此;应该复制该块并分配副本,因为任何获得常规对象指针值的人都不会进行任何块复制注意事项。

By the way, this is also true for any other case where a block-pointer type is assigned or converted to a regular object-pointer type; the block should be copied and the copy assigned, because anyone who gets the regular object-pointer value wouldn't be expected to do any block copying considerations.

ARC使情况有些复杂化。 ARC规范指定隐式复制块的一些情况。例如,当存储到编译时块指针类型的变量(或ARC要求保留编译时块指针类型的值的任何其他位置)时,ARC要求复制传入值而不是保留,所以程序员不必担心在这些情况下显式复制块。

ARC complicates the situation somewhat. The ARC specification specifies some situations where blocks are implicitly copied. For example, when storing to a variable of compile-time block-pointer type (or any other place where ARC requires a retain on a value of compile-time block-pointer type), ARC requires that the incoming value be copied instead of retained, so the programmer doesn't have to worry about explicitly copying blocks in those cases.


除了作为初始化的一部分完成的保留外
__ strong 参数变量或读取 __弱变量,每当
这些语义要求保留块指针类型的值,
具有 Block_copy 的效果。

With the exception of retains done as part of initializing a __strong parameter variable or reading a __weak variable, whenever these semantics call for retaining a value of block-pointer type, it has the effect of a Block_copy.

但是,作为例外,ARC规范不保证仅在复制参数时传递块。

However, as an exception, the ARC specification does not guarantee that blocks only passed as arguments are copied.


当优化器看到结果是
仅用作调用的参数时,优化器可以删除这些副本。

The optimizer may remove such copies when it sees that the result is used only as an argument to a call.

所以w hether明确地复制作为参数传递给函数的块仍然是程序员必须考虑的事情。

So whether to explicitly copy blocks passed as arguments to a function is still something the programmer has to consider.

现在,Apple的Clang编译器的最新版本中的ARC实现没有记录功能,它将隐式块副本添加到作为参数传递块的某些位置,即使ARC规范不需要它。 (未记录,因为我无法找到任何Clang文档。)特别是,当将块指针类型的表达式传递给非块对象指针类型的参数时,它似乎总是在防御性地添加隐式副本。实际上,正如CRD所证明的那样,它在从块指针类型转换为常规对象 - 指针类型时也会添加隐式副本,因此这是更一般的行为(因为它包含参数传递大小写)。

Now, the ARC implementation in recent versions of Apple's Clang compiler has an undocumented feature where it will add implicit block copies to some of the places where blocks are passed as arguments, even though the ARC specification doesn't require it. ("undocumented" because I cannot find any Clang documentation to this effect.) In particular, it appears that it defensively always adds implicit copies when passing an expression of block-pointer type to a parameter of non-block object pointer type. In fact, as demonstrated by CRD, it also adds an implicit copy when converting from a block-pointer type to a regular object-pointer type, so this is the more general behavior (since it includes the argument passing case).

但是,当将块指针类型的值作为varargs传递时,看起来当前版本的Clang编译器不会添加隐式副本。 C varargs不是类型安全的,调用者不可能知道函数期望的类型。可以说,如果Apple想要在安全方面出错,因为无法知道函数的期望,他们也应该在这种情况下添加隐式副本。但是,既然这件事都是无证件的,我不会说这是一个错误。在我看来,程序员不应该依赖于仅作为参数传递的块,而这些块首先被隐式复制。

However, it appears that the current version of the Clang compiler does not add implicit copies when passing a value of block-pointer type as varargs. C varargs are not type-safe, and it is impossible for the caller to know what types the function expects. Arguably, if Apple wants to error on the side of safety, since there's no way of knowing what the function expects, they should add implicit copies always in this case too. However, since this whole thing is an undocumented feature anyway, I wouldn't say it's a bug. In my opinion, then programmer should never rely on blocks that are only passed as arguments being implicitly copied in the first place.

这篇关于了解objc中块内存管理的一个边缘情况的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆