美文网首页
BLOCK 基础到深入

BLOCK 基础到深入

作者: 孢子菌 | 来源:发表于2020-02-11 00:42 被阅读0次

    一 简介

    Objective-C 中的block,是匿名函数,匿名函数在别的语言中也被称作闭包、λ表达式等。Objective-C 中的 block,有以下几个特性:

    1. 可以将block作为函数,也能作为对象,当做属性持有,定义内存修饰词。
    2. 可以长期存在,出了定义的作用域,内部实现也能执行。
    3. 可以捕获外部变量,甚至使用__block修饰后,block 可以修改这个捕获变量。

    这背后的原理是什么呢,看了很多博客和代码,理清楚了其中奥秘,在这里总结一下。

    二 结构

    block的结构,用clang -rewrite-objcobjc代码转成cpp代码,就能很清楚的看到了。在cpp中,block实际被编译器转写成结构体了。结构体的介绍网上已经有很多完善的资料了,我这里直接参考了这篇博客

    // objc 代码
    int test()
    {
        void (^blk)(void) = ^{
            printf("Block\n");
        };
        blk();
        return 0;
    }
    
    // cpp 重写后
    // 自己定义的 test 方法
    int test()
    {
        // 声明&赋值
        void (*blk)(void) = ((void (*)())&__test_block_impl_0/*构造函数*/(
                                          (void *)__test_block_func_0,
                                          &__test_block_desc_0_DATA)
                                          );
        // 执行
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return 0;
    }
    
    // 我们创建的 block 的结构体
    struct __test_block_impl_0 {
      struct __block_impl impl; // 核心:函数指针
      struct __test_block_desc_0* Desc; // 描述
      // 构造函数
      __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;// 栈 block
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    struct __block_impl {
      void *isa; // isa指针,所以是objc对象
      int Flags;
      int Reserved;
      void *FuncPtr; // 函数指针
    };
    
    // 静态函数指针
    static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
        printf("Block\n");
    }
    
    // 描述
    static struct __test_block_desc_0 {
      size_t reserved;
      size_t Block_size; // 大小
    } __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)}; // __test_block_desc_0_DATA 是这个结构体的实例
    

    这个结构刚才引用的博客中已经分析的很透彻了,这里就不一步步细讲了。直接说一下我总结的结构。

    __xx_block_impl_0 // block完整结构体
    {
        __block_impl, // 核心结构,具体结构在下面
        desc, // block 描述,具体结构在下面
        int foo, // 捕获的变量,依次排列开
        ...
        __xx_block_impl_0(fp, desc, param0,param1...)//构造函数
    }
    
    struct __block_impl
    {
        id isa, // isa 指针
        void *fp, // 函数指针
        flag // 标记位,用来描述 block 类型
    }
    
    block_desc // block 描述
    {
        size,// 大小
        void *copy,// 捕获对象时,捕获变量们的copy 函数
        void *dispose// 捕获对象时,捕获变量们的dispose 函数
    }
    // 静态函数指针
    static void __xx_block_func_0() {
        // 具体实现
    }
    

    在讲一下该结构引出的比较让人疑惑的点:

    1. 一个 block 定义会被编译器clang编译生成一套特殊结构体,依据block行为不同(入参、捕获变量),生成的结构体也不一样。从结构体名称类名__方法名__block__impl__序号也可以知道,它是动态创建的。
      相关的创建详情可以参考 clanggenCode 模块,GCBlocks
    2. 结构体是对象,分成三类:
    • 栈block_NSConcreteStackBlock
    • 全局block_NSConcreteGlobalBlock
    • 堆block_NSConcreteMallocBlock

    详细的区别参考这里。这里只说一点,栈block在赋值时,会被拷贝到堆上,这样就通过引用计数管理了(引用计数的内容可以看我的博客坑位、待填)。
    关于这个拷贝操作,开始我并没有在代码中看见,只看见了赋值void (*blk)(void) = ((void (*)())&__test_block_impl_0...,找了很久,才发现真实依据是NSObject.mm

    // The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
    id objc_retainBlock(id x) {
        return (id)_Block_copy(x);
    }
    
    1. 一个奇怪的细节:block声明时,得到的是__test_block_impl_0类型变量 blk;执行时,却转成了__block_impl类型。
      这里是因为__block_impl__test_block_impl_0的第一个变量,二者的指针地址相同的,所以可以直接强转,详见这篇的 2.3 部分

    三 参数捕获

    上面说到block捕获的变量不同,动态生成的结构也不一样。block可以捕获:objc 对象基础数据类型另一个 block。我们常用的是捕获基础数据类型和 objc 对象基础数据类型
    下面就常用情况再分成四种情况讨论:

    这部分有大牛珠玉在前,解释的比较透彻了。所以我就简单总结一下:

    1. 捕获基础数据,在 block 结构中,会将捕获参数添加进去。前面讲了,在 block 赋值时,会被执行一个 _Block_copy 操作,这其中对整个 block 结构进行拷贝,捕获的基础数据值也会被拷贝到堆上。
    2. 捕获对象变量时,除了会被添加到block结构体中,还会额外生成一对被捕获变量的拷贝函数销毁函数,保存在__block_desc结构中。这里面描述了被捕获的变量,如何被拷贝到堆上去。
    // 捕获变量的copy
    // _Block_copy 的时候会调用
    // _Block_copy 在 block 被赋值时候调用
    static void __TestClass__testMethod_block_copy_2(struct __TestClass__testMethod_block_impl_2*dst, struct __TestClass__testMethod_block_impl_2*src) {
        _Block_object_assign((void*)&dst->tmpB, (void*)src->tmpB, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    // 捕获变量的dispose
    // _Block_release 的时候会调用
    // _Block_release 的调用时机是堆 block 引用计数为 0 时
    static void __TestClass__testMethod_block_dispose_2(struct __TestClass__testMethod_block_impl_2*src) {
        _Block_object_dispose((void*)src->tmpB, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    1. 捕获__block修饰的基础数据,该基础数据会被转化成一个特殊的结构体:
    // 定义处,在栈上
    __block int tmpB = 1;
    void(^blk003)(int a) = ^(int a) {
        // 使用处,在堆上
        NSLog(@"tmpB=%d", tmpB);
    };
    
    // __block 修饰的 int 变量 tempB
    struct __Block_byref_tmpB_0 {
      void *__isa;
    __Block_byref_tmpB_0 *__forwarding; 
     int __flags;
     int __size;
     int tmpB;
    };
    

    我们称它为byref 封装,可以看到它也是一个 objc 对象。所以byref 封装和被捕获变量一样,会被添加到block 结构里之外,还会生成copydispose函数指针。
    byref 封装除了封装了自身的实际值之外,还持有一个自己类型的__forwarding指针。当byref 封装在栈上的时候,__forwarding指针会指向它堆上的拷贝,在堆上的时候回指向自己。这样就保证了定义处和使用处调用方式是一样的,至于源码是如何实现的,下面会讲到。

    1. 捕获__block修饰的对象,同样也会生成一个byref 封装,结构也和上一条类似:
     __block NSString * tempC = [NSString stringWithFormat:@"1"];
     void (^test)() = ^ {
            NSLog(@"%@",tempC);
     };
    
    // __block 修饰的 int 变量 tempC
    struct __Block_byref_tempC_0 {
      void *__isa;
    __Block_byref_tmpC_0 *__forwarding; 
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);//实际赋值__Block_byref_id_object_copy_131
     void (*__Block_byref_id_object_dispose)(void*);//同上
     NSString *tempC;
    };
    
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    
    static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    

    但是多处量函数指针变量,这俩变量在创建时,被传值__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131。这俩在byref 封装拷贝时会调用到,用来拷贝NSString *tempC的。

    总结:可以看到 __xx_block_impl_0结构随着捕获变量的复杂而变得复杂。而block从栈到堆的拷贝,是由自身结构->捕获变量->byref的value,层层进行的。

    四 堆 block 拷贝

    上面总结了拷贝的流程,这一节撸一下代码,一来加深印象,二来找下几个常见问题的答案:

    1. 在 block 中被修改了,栈上的原值也会被修改吗?
    2. 循环引用是如何产生的,weak-strong-dance 为何能解决?
    3. 使用weak-strong-dance,被引用对象何时会为 nil ?

    block 结构体的拷贝方法 _Block_copy

    // Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
    void *_Block_copy(const void *arg) {
        struct Block_layout *aBlock;
    
        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_GLOBAL) {
            return aBlock;
        }
        else {
            // Its a stack block.  Make a copy.
           // !!!!开辟堆上内存空间
            struct Block_layout *result =
                (struct Block_layout *)malloc(aBlock->descriptor->size);
            if (!result) return NULL;
            // !!!!将内容移到指定堆上
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    #if __has_feature(ptrauth_calls)
            // Resign the invoke pointer as it uses address authentication.
            result->invoke = aBlock->invoke;
    #endif
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
            result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
            _Block_call_copy_helper(result, aBlock);
            // Set isa last so memory analysis tools see a fully-initialized object.
            result->isa = _NSConcreteMallocBlock;
            return result;
        }
    }
    

    _Block_call_copy_helper 参数拷贝助手

    static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
    {
        struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
        if (!desc) return;
    
        (*desc->copy)(result, aBlock); // do fixup
    }
    
    static void __TestClass__testMethod_block_copy_2(struct __TestClass__testMethod_block_impl_2*dst, struct __TestClass__testMethod_block_impl_2*src) {
        _Block_object_assign((void*)&dst->tmpB, (void*)src->tmpB, 8/*BLOCK_FIELD_IS_BYREF*/);
        _Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    

    _Block_object_assign 参数拷贝函数

    void _Block_object_assign(void *destArg, const void *object, const int flags) {
        const void **dest = (const void **)destArg;
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_OBJECT:
            /*******
            id object = ...;
            [^{ object; } copy];
            ********/
    
            _Block_retain_object(object);
    // static void _Block_retain_object_default(const void *ptr __unused) { }
    // static void _Block_destructInstance_default(const void *aBlock __unused) {}
    // !!!!都是空方法
            *dest = object;
            break;
    
          case BLOCK_FIELD_IS_BLOCK:
            /*******
            void (^object)(void) = ...;
            [^{ object; } copy];
            ********/
    // !!!!block 参数,再调一次 `_Block_copy`
            *dest = _Block_copy(object);
            break;
        
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
            /*******
             // copy the onstack __block container to the heap
             // Note this __weak is old GC-weak/MRC-unretained.
             // ARC-style __weak is handled by the copy helper directly.
             __block ... x;
             __weak __block ... x;
             [^{ x; } copy];
             ********/
    // !!!!__block 修饰的基础数据类型
            *dest = _Block_byref_copy(object);
            break;
            
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
            /*******
             // copy the actual field held in the __block container
             // Note this is MRC unretained __block only. 
             // ARC retained __block is handled by the copy helper directly.
             __block id object;
             __block void (^object)(void);
             [^{ object; } copy];
             ********/
    // !!!!__block 修饰的对象
    // !!!!直接赋值,对其引用计数+1
            *dest = object;
            break;
    
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
            /*******
             // copy the actual field held in the __block container
             // Note this __weak is old GC-weak/MRC-unretained.
             // ARC-style __weak is handled by the copy helper directly.
             __weak __block id object;
             __weak __block void (^object)(void);
             [^{ object; } copy];
             ********/
    // !!!!__block __weak 双重修饰的对象
    // !!!!直接赋值,对其引用计数+1
    // !!!!相当于 __weak 没有起作用
            *dest = object;
            break;
    
          default:
            break;
        }
    }
    

    _Block_byref_copy__forwarding 指针的操作

    // Runtime entry points for maintaining the sharing knowledge of byref data blocks.
    
    // A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
    // Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
    // We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
    // Otherwise we need to copy it and update the stack forwarding pointer
    static struct Block_byref *_Block_byref_copy(const void *arg) {
        struct Block_byref *src = (struct Block_byref *)arg;
     // !!!! 通过引用计数来判断原 byref 的 forwarding 是否为堆上变量
        if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            // src points to stack
            struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
            copy->isa = NULL;
            // byref value 4 is logical refcount of 2: one for caller, one for stack
            copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
     // !!!! 堆上 byref 指向自己地址本身
            copy->forwarding = copy; // patch heap copy to point to itself
     // !!!! 原栈上 byref 指向堆上地址
            src->forwarding = copy;  // patch stack to point to heap copy
            copy->size = src->size;
     // !!!! 判断有 copy 或 dispose 函数,会执行相应函数
            if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                // Trust copy helper to copy everything of interest
                // If more than one field shows up in a byref block this is wrong XXX
                struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
                struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
                copy2->byref_keep = src2->byref_keep;
                copy2->byref_destroy = src2->byref_destroy;
    
                if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                    struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                    struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                    copy3->layout = src3->layout;
                }
    
                (*src2->byref_keep)(copy, src);
            }
            else {
                // Bitwise copy.
                // This copy includes Block_byref_3, if any.
                memmove(copy+1, src+1, src->size - sizeof(*src));
            }
        }
        // already copied to heap
        else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
    //!!!! 引用计数+1 难道所有对象的引用计数,都是用 flag 来表示的??
            latching_incr_int(&src->forwarding->flags);
        }
        
        return src->forwarding;
    }
    
    
    static int32_t latching_incr_int(volatile int32_t *where) {
        while (1) {
            int32_t old_value = *where;
            if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
                return BLOCK_REFCOUNT_MASK;
            }
            if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
                return old_value+2;
            }
        }
    }
    

    五 sunnyxx 面试题

    看完了上面内容,可以用下面的几道题测试下自己的掌握程度


    sunnyxx面试题.png

    参考:
    破弓的《iOS Block》系列
    libclosure 源码
    objc4 源码
    https://www.jianshu.com/p/51d04b7639f1
    https://www.jianshu.com/p/b554e813fce1
    https://www.jianshu.com/p/e42f86a81045
    http://clang.llvm.org/docs/Block-ABI-Apple.html#block-escapes
    GCBlocks

    相关文章

      网友评论

          本文标题:BLOCK 基础到深入

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