美文网首页
Objective-C中的block到底是个啥(三)

Objective-C中的block到底是个啥(三)

作者: d8893ea8ba05 | 来源:发表于2017-12-25 15:20 被阅读40次

    这篇我们主要来看下blcok在copy和release的时候都做了什么。关于block,要时刻牢记一点,如果是在栈中创建的block,那么如果后面需要再次饮用该block,需要将其拷贝到堆上。那么在拷贝的时候都发生了些什么呢?比如block捕获的外部变量如何处置的?

    block的结构

    从前两个文章中我们已经知道了block的结构如下:


    block结构

    在第二篇文章中我们看到了一个在栈上被创建出来的block,即然是在栈上创建的,那么在当前上下文结束后,栈上的内存就有可能被回收重复利用。那么如果后面想要再次饮用该block就需要将它拷贝到堆上。这个过程是通过Block_copy() 来完成的。
    下面我们就来看下Block_copy() 这个方法

    Block_copy()

    在compiler-rt项目源码中找到Block.h文件,可以看到关于如下定义:

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

    可以看到Block_copy()就是将参数传入到const void *并将它传入给_Block_copy函数。在runtime.c文件中可以查看_Block_copy方法:

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

    然后继续查找_Block_copy_internal方法:

    static void *_Block_copy_internal(const void *arg, const int flags) {
        struct Block_layout *aBlock;
        const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
    
        //printf("_Block_copy_internal(%p, %x)\n", arg, flags); 
        if (!arg) return NULL;
        
        
        // The following would be better done as a switch statement
        aBlock = (struct Block_layout *)arg;
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }
        else if (aBlock->flags & BLOCK_IS_GC) {
            // GC refcounting is expensive so do most refcounting here.
            if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 1)) {
                // Tell collector to hang on this - it will bump the GC refcount version
                _Block_setHasRefcount(aBlock, true);
            }
            return aBlock;
        }
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            return aBlock;
        }
    
        // Its a stack block.  Make a copy.
        if (!isGC) {
            struct Block_layout *result = malloc(aBlock->descriptor->size);
            if (!result) return (void *)0;
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
            result->flags |= BLOCK_NEEDS_FREE | 1;
            result->isa = _NSConcreteMallocBlock;
            if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
                //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
                (*aBlock->descriptor->copy)(result, aBlock); // do fixup
            }
            return result;
        }
        else {
            // Under GC want allocation with refcount 1 so we ask for "true" if wantsOne
            // This allows the copy helper routines to make non-refcounted block copies under GC
            unsigned long int flags = aBlock->flags;
            bool hasCTOR = (flags & BLOCK_HAS_CTOR) != 0;
            struct Block_layout *result = _Block_allocator(aBlock->descriptor->size, wantsOne, hasCTOR);
            if (!result) return (void *)0;
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
            // reset refcount
            // if we copy a malloc block to a GC block then we need to clear NEEDS_FREE.
            flags &= ~(BLOCK_NEEDS_FREE|BLOCK_REFCOUNT_MASK);   // XXX not needed
            if (wantsOne)
                flags |= BLOCK_IS_GC | 1;
            else
                flags |= BLOCK_IS_GC;
            result->flags = flags;
            if (flags & BLOCK_HAS_COPY_DISPOSE) {
                //printf("calling block copy helper...\n");
                (*aBlock->descriptor->copy)(result, aBlock); // do fixup
            }
            if (hasCTOR) {
                result->isa = _NSConcreteFinalizingBlock;
            }
            else {
                result->isa = _NSConcreteAutoBlock;
            }
            return result;
        }
    }
    

    该函数做了如下的逻辑(忽略掉GC的部分):
    1、如果参数为空则直接返回空
    2、将第一个参数转换为Block_layout,从第一篇文章中可以知道该结构体就是block的完整表示
    3、如果block的flag包含BLOCK_NEEDS_FREE,也就是该block本来就在堆上,那么直接引用计数加1,然后返回该block对象
    4、如果是BLOCK_IS_GLOBAL也就是全局block,那么不用引用计数直接返回
    5、能走到这一步说明传入的block肯定是在栈中创建,要拷贝到堆上,第一步是要在堆上分配内存,这一步通过mallock完成
    6、然后通过memmove方法将参数中的内容按字节拷贝到刚刚分配的堆内存中
    7、接下来就是设置堆上block的正确状态,BLOCK_REFCOUNT_MASK用来保证引用计数初始化为0,然后BLOCK_NEEDS_FREE用来设置该block为堆上的block,当引用计数为0时需要释放内存,并且通过 | 1 将引用计数设置为1.
    8、将isa设置为_NSConcreteMallocBlock,也是用来标识堆block
    9、最后,如果block有copy辅助方法,那么就通过调用该方法来retain需要获取的外部变量

    Block_release

    与copy对应的就是release,在Block.h文件中同样可以找到Block_release的定义:

    BLOCK_EXPORT void _Block_release(const void *aBlock);
    #define Block_release(...) _Block_release((const void *)(__VA_ARGS__))
    

    然后在runtime.c中可以找到_Block_release的定义:

    void _Block_release(void *arg) {
        struct Block_layout *aBlock = (struct Block_layout *)arg;
        int32_t newCount;
        if (!aBlock) return;
        newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
        if (newCount > 0) return;
        // Hit zero
        if (aBlock->flags & BLOCK_IS_GC) {
            // Tell GC we no longer have our own refcounts.  GC will decr its refcount
            // and unless someone has done a CFRetain or marked it uncollectable it will
            // now be subject to GC reclamation.
            _Block_setHasRefcount(aBlock, false);
        }
        else if (aBlock->flags & BLOCK_NEEDS_FREE) {
            if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
            _Block_deallocator(aBlock);
        }
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            ;
        }
        else {
            printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
        }
    }
    

    这里的逻辑如下(忽略掉GC的部分):
    1、将参数转换为Block_layout结构体,如果为空则返回
    2、将block的引用计数减1
    3、判断引用计数,如果大于0则说明该block仍被某个对象持有,不应该释放,直接返回
    4、如果该block为堆block,那么调用dispose辅助方法来释放掉之前retain的外部变量,然后调用_Block_deallocator方法,该方法最终调用了free来释放堆上分配的内存
    5、如果是个全局block,那么啥都不做
    6、如果代码还能走到这里的话,说明正在试图释放一个栈上的block,正常是不应该有这种操作的,于是就打印了一串警告。

    相关文章

      网友评论

          本文标题:Objective-C中的block到底是个啥(三)

          本文链接:https://www.haomeiwen.com/subject/hxelgxtx.html