(译)窥探Blocks(3) Block_copy

第一篇文章第二篇文章我们已经研究了一些blocks的内部原理了。本文将进一步研究block拷贝的过程。你可能听到过一些术语比如"blocks 起始于栈"以及"如果想保存它们以后用你必须拷贝"。但是为什么呢?拷贝到底做了什么事?我长久以来一直在好奇拷贝block的机制到底是什么。比如block捕获的值会怎么样。本文我将对此做些阐述。

我们已经知道的事

第一篇文章第二篇文章中我们知道一个block的内存布局长这样:

block内存布局

第二篇文章中我们看到block最开始被引用的时候是在栈上创建的。既然是在栈上,那么在block的封闭域结束后内存就会被回收重用。那你之后再用这个block会发生什么呢?好吧,你必须拷贝它。这是通过调用Block_copy()方法或者直接向他发送OC的copy消息完成。这就是所谓的Block_copy()

Block_copy()

首先我们来看Block.h。其中有下面的定义:

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))

void *_Block_copy(const void *arg);

所以Block_copy是一个宏,它将传入的参数转换为一个const void *然后传递给_Block_copy()方法。_Block_copy()的实现在runtime.c

void *_Block_copy(const void *arg) {
    return _Block_copy_internal(arg, WANTS_ONE);
}

所以也就是调用_Block_copy_internal方法,传入block自己和WANTS_ONE。为了明白这什么意思,我们需要看一下实现代码。也在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;
}

主要做了以下工作:

  1. 如果传入参数是NULL就直接返回NULL。防止传入一个NULL的Block。

  2. 将参数转换为一个struct Block_layout类型的指针。你也许还记得第一篇文章中提到它。它就是block内部一个包含了实现函数和一些元数据的数据结构。

  3. 如果block的flags字段包含BLOCK_NEEDS_FREE,那么这是一个堆block(稍后你就明白)。这里只需要增加引用计数然后返回原blcok。

  4. 如果这是一个全局block(回看第一篇文章),那么不需要做任何事,直接返回原block。因为全局block是一个单例。

  5. 如果走到这里,那么这一定是一个栈上分配的block。那样的话,block需要拷贝到堆上。这才是有趣的部分。第一步,调用malloc()创建一块特定的内存。如果创建失败,返回NULL;否则,继续。

  6. 调用memmove()方法将当前栈上分配的block按位拷贝到我们刚刚创建的堆内存上。这样可以保证所有的元数据都拷贝过来,比如descriptor。

  7. 接下来,更新标志位。第一行确保引用计数为0。注释表明这行其实不需要——大概这个时候引用计数已经是0了。我猜保留这行是因为以前有个bug导致这里的引用计数不是0(所以说runtime的代码也会偷懒)。下一行设置了BLOCK_NEEDS_FREE标志位,表明这是一个堆block,一旦引用计数减为0,它所占用的内存将被释放。|1操作设置block的引用计数为1。

  8. block的isa指针被设置为_NSConcreteMallocBlock,说明这是个堆block。

  9. 最后,如果block有一个拷贝辅助函数,那么它将被调用。必要的时候编译器会生成拷贝辅助函数。比如一个捕获了对象的block就需要。那么拷贝辅助函数将持有被捕获的对象。

哈哈,已经十分清晰了。现在你知道拷贝一个block到底发生了什么事!但那只是图片展示的一半内容,对吧?释放一个block又会怎么样呢?

Block_release()

Block_copy()图的另一半是Block_release()。实际上这又是一个宏:

#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

Block_copy()一样,Block_release()也是转换传入的参数然后调用一个方法。这一定程度上解放了程序员的双手,他们不用自己做转换。

我们来看看_Block_release()的源码(简明起见,重新整理了代码顺序,并删除了垃圾回收相关的代码):

    // 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);
    }
}

这段代码做了这些事:

  1. 首先,参数被转换为一个指向struct Block_layout的指针。如果传入NULL,直接返回。

  2. 标志位部分表示引用计数减1(之前Block_copy()中标志位操作代表的是引用计数置为1)。

  3. 如果新的引用计数值大于0,说明有其他东西在引用block,所以block不应该被释放。

  4. 否则,如果标志位包含BLOCK_NEEDS_FREE,那么这是一个堆block而且引用计数为0,应该被释放。首先block的处理辅助函数(dispose helper)被调用,它是拷贝辅助函数(copy helper)的反义词,执行相反的操作,比如释放被捕获的对象。最后调用_Block_deallocator方法释放block。如果你查找runtime.c你就会发现这个方法最后就是一个free的函数指针,释放malloc分配的内存。

  5. 如果到这一步且lock是全局的,什么也不做。

  6. 如果到这一步,一定是发生了未知状况,因为一个栈block试图在这里释放,输出一行警告。实际上,你应该永远不会走到这一步。

这些就是Block! 东西也并不多嘛(呵呵)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,728评论 10 69
  • 目录 Block底层解析什么是block?block编译转换结构block实际结构block的类型NSConcre...
    tripleCC阅读 33,050评论 32 388
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 本文为转载: 作者:zyydeveloper 链接:http://www.jianshu.com/p/5f776a...
    Buddha_like阅读 823评论 0 2
  • 307、setValue:forKey和setObject:forKey的区别是什么? 答:1, setObjec...
    AlanGe阅读 1,478评论 0 1