块拷贝()
首先,我们得看看[Block.h](https://link.zhihu.com/?target=http%3A//opensource.apple.com/source/clang/clang-137/src/projects/compiler-rt/BlocksRuntime/Block.h)。这里有以下定义:
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
void *_Block_copy(const void *arg);
所以Block_copy()纯粹是#define,它将传入的参数转换为const void *把它传递给_Block_copy()。还有一个原型_Block_copy()。实施在[runtime.c](https://link.zhihu.com/?target=http%3A//opensource.apple.com/source/clang/clang-137/src/projects/compiler-rt/BlocksRuntime/runtime.c):
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}
所以这只是打电话_Block_copy_internal()通过街区本身WANTS_ONE。为了了解这意味着什么,我们需要查看实现。这也是在[runtime.c](https://link.zhihu.com/?target=http%3A//opensource.apple.com/source/clang/clang-137/src/projects/compiler-rt/BlocksRuntime/runtime.c)。下面是一个函数,删除了不相关的东西(大部分是垃圾收集的东西):
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1
if (!arg) return NULL;
// 2
aBlock = (struct Block_layout *)arg;
// 3
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8
result->isa = _NSConcreteMallocBlock;
// 9
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
这个方法就是这样做的:
iOS开发交流技术群:[欢迎你的加入](正在跳转),不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
- 如果传递的参数是
NULL然后再回来NULL。这使得该方法可以安全地传递NULL封锁。 - 将参数转换为指向
struct Block_layout。你可能还记得其中一个第1集。内部数据结构构成了一个块,包括指向块的实现函数的指针和各种元数据位。 - 如果块的标志包括
BLOCK_NEEDS_FREE然后,这个块就是一个堆块(您很快就会看到原因)。在这种情况下,所需要做的就是增加引用计数,然后返回相同的块。 - 如果该块是全局块(请从第1集)然后什么都不需要做,并且返回相同的块。这是因为全局块实际上是单个块。
- 如果我们已经到达这里,那么这个块必须是一个堆栈分配块。在这种情况下,需要将块复制到堆中。这是有趣的部分。在第一步,
malloc()用于创建所需大小的内存的一部分。如果失败了,那么NULL返回,否则我们会继续。 - 这里,
memmove()用于将位对位然后是当前的堆栈分配块复制到我们刚刚分配给堆分配块的内存部分。这只会确保所有元数据都被复制,例如块描述符。 - 接下来,更新块的标志。第一行确保将引用计数设置为0。注释指出这是不需要的--大概是因为此时引用计数应该已经是0。我想这行是为了防止错误存在,而引用计数不是0。下一行设置
BLOCK_NEEDS_FREE旗子。这表明它是一个堆块,一旦引用计数降到零,它的内存备份就需要free-英灵。这个| 1在这一行中,将块的引用计数设置为1。 - 这里是街区
isa指针设置为_NSConcreteMallocBlock这意味着它是一个堆块。 - 最后,如果块有一个复制助手函数,那么就会调用这个函数。如果需要,编译器将生成复制助手函数。例如,捕获对象的块需要它。在这种情况下,复制助手函数将保留捕获的对象。
挺不错的,嗯!现在你知道当一个块被复制时会发生什么了!但这只是照片的一半,对吧?当一个人被释放的时候呢?
块释放()
另一半Block_copy()图片是Block_release()。再一次,这实际上是一个宏,如下所示:
1#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))
就像Block_copy(), Block_release()在为我们传递参数之后调用一个函数。这只是帮助开发人员,这样他们就不需要自己了。
让我们看看_Block_release()(为清晰性和垃圾收集特定代码略作重新排列):
void _Block_release(void *arg) {
// 1
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
// 2
int32_t newCount;
newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
// 3
if (newCount > 0) return;
// 4
if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
_Block_deallocator(aBlock);
}
// 5
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
;
}
// 6
else {
printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
}
}
以下是每个片段的作用:
-
首先,将参数转换为指向
struct Block_layout因为那就是它的本质。如果NULL,则我们提前返回,以使函数不受传入的影响。NULL. -
在这里,块标志中表示引用计数的部分(从
Block_copy()将标志设置为指示引用计数(1)的部分将减少。 -
如果新的计数大于0,那么仍然存在对该块的引用,因此还不需要释放该块。
-
否则,如果标志包括
BLOCK_NEEDS_FREE,然后这是一个堆分配块,引用计数为0,因此应该释放该块。但是,首先调用块的Dispose助手函数。这是复制助手函数的反义词。它执行相反的操作,例如释放任何捕获的对象。最后,通过使用_Block_deallocator。如果你去打猎[runtime.c](https://link.zhihu.com/?target=http%3A//opensource.apple.com/source/clang/clang-137/src/projects/compiler-rt/BlocksRuntime/runtime.c)然后你会发现这最终是一个指向free,这只会释放分配给malloc. -
如果我们到了这里,而且这个街区是全球性的,那就什么也不做。
-
如果我们一路走到这里,那么会发生一些奇怪的事情,因为一个堆栈块试图被释放,所以打印了一个日志行来警告开发人员。在现实中,你不应该看到它被击中。