美文网首页底层
iOS-OC底层24:Block底层原理

iOS-OC底层24:Block底层原理

作者: MonKey_Money | 来源:发表于2020-11-13 08:47 被阅读0次

    1.Block的类型

    全局Block(NSGlobalBlock)

      void (^block)(void) = ^{
            NSLog(@"------");
        };
        NSLog(@"%@",block);
    //打印结果<__NSGlobalBlock__: 0x10bb2d030>
    

    block 内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。如果只引用全局变量和静态变量也是全局Block。

    堆Block(NSMallocBlock)

     int a = 10;
        void (^block)(void) = ^{
            NSLog(@"----%d",a);
        };
        NSLog(@"%@",block);
    打印结果
    <__NSMallocBlock__: 0x60000179d950>
    

    NSMallocBlock只需要对NSStackBlock进行copy操作就可以获取

    栈Block(NSStackBlock)

    之前我们通过

    int a= 10;
     NSLog(@"%@",^{
            NSLog(@"----%d",a);
        });
    

    可以得出StackBlock,但是现在不行了,我们可以通过下面方式得出StackBlock

        int a = 10;
        void ( __weak ^block)(void) = ^{
            NSLog(@"----%d",a);
        };
    
        // block_copy
        NSLog(@"%@",block);
    打印结果是
    <__NSStackBlock__: 0x7ffeed6d53f8>
    

    2.Block的循环引用和解决

    循环引用

    在ViewController声明两个属性

    typedef void(^MyVoidBlock)(void);
    @property (nonatomic, copy) MyVoidBlock myVoidBlock;
    @property (nonatomic, copy) NSString *name;
      self.name = @"iOS";
        self.myVoidBlock = ^{
            NSLog(@"%@",self.name);
        };
    

    我们会看到有warning :Capturing 'self' strongly in this block is likely to lead to a retain cycle
    提示循环引用,当我们退出ViewController时,dealloc没有被调用


    BlockRetain.png

    循环引用的解决

    1.__weak和__strong

       __weak typeof(self) weakSelf = self;
        self.myVoidBlock = ^{
            NSLog(@"%@",weakSelf.name);
        };
        self.myVoidBlock();
    

    当我们退出VC时,delloc方法确实走到了,说明循环引用解决了。但是如果block内存在耗时操作,当我们VC退出后,才会调用weakSelf呢?

        __weak typeof(self) weakSelf = self;
        self.myVoidBlock = ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"%@",weakSelf.name);
    
            });
        };
        self.myVoidBlock();
    -(void)dealloc {
        NSLog(@"%s",__func__);
    }
    

    dealloc确实走了,但是我们的打印名字出现了问题

     -[ViewController dealloc]
    (null)
    

    这明显不是我们希望的,我们希望打印完名字之后对象再被释放。

      self.name = @"iOS";
        __weak typeof(self) weakSelf = self;
        self.myVoidBlock = ^{
            __strong typeof(weakSelf)  strongSelf = weakSelf;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"%@",strongSelf.name);
    
            });
        };
        self.myVoidBlock();
    

    打印按照我们的预期打印。
    2.__block解决循环引用

       __block ViewController *vc = self;
        self.myVoidBlock = ^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"%@",vc.name);
                vc = nil;
            });
        };
        self.myVoidBlock();
    

    打印结果正常

    iOS
    -[ViewController dealloc]
    

    但是如果block没被调用则ViewController对象不会被释放
    3.作为参数解决循环引用

        self.myVoidBlock = ^(ViewController *vc){
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"%@",vc.name);
    
            });
        };
        self.myVoidBlock(self);
    

    打印结果正常

    3.Block的底层原理

    1.汇编看Block底层

    image.png

    在block前面打断点,看汇编


    image.png

    我们看到关键信息objc_retainBlock,在objc源码中看出objc_retainBlock底层实现时
    _Block_copy。下符号断点objc_retainBlock研究
    1.全局block

      void(^block)(void) = ^{
            NSLog(@"----");
        };
    

    在符号断点处objc_retainBlock,读取寄存器,

     register read x0
          x0 = 0x0000000102388028  001---Block深入浅出`__block_literal_global
     po 0x0000000102388028
    <__NSGlobalBlock__: 0x102388028>
     signature: "v8@?0"
     invoke   : 0x102386288 (/private/var/containers/Bundle/Application/00B30C0F-7E4D-4B6C-B351-9EB5A91223DA/001---Block深入浅出.app/001---Block深入浅出`__29-[ViewController viewDidLoad]_block_invoke)
    

    2.堆block

      int a= 10;
        void(^block)(void) = ^{
            NSLog(@"----%d",a);
        };
    

    同样在objc_retainBlock符号断点处,读寄存器信息

    ) register read x0
          x0 = 0x000000016f40b4f8
     po 0x000000016f40b4f8
    <__NSStackBlock__: 0x16f40b4f8>
     signature: "v8@?0"
     invoke   : 0x1009f6264 (/private/var/containers/Bundle/Application/A4407F87-FF50-4D6F-AA84-CCA6CDA6BCA6/001---Block深入浅出.app/001---Block深入浅出`__29-[ViewController viewDidLoad]_block_invoke)
    

    单步调试,在objc_retainBlock返回时读寄存器

    register read x0
          x0 = 0x0000000281e3a0d0
     po 0x0000000281e3a0d0
    <__NSMallocBlock__: 0x281e3a0d0>
     signature: "v8@?0"
     invoke   : 0x1009f6264 (/private/var/containers/Bundle/Application/A4407F87-FF50-4D6F-AA84-CCA6CDA6BCA6/001---Block深入浅出.app/001---Block深入浅出`__29-[ViewController viewDidLoad]_block_invoke)
    

    2.结合源码看block签名信息

    我们在Block_private.h先查看Block的结构源码地址

    #define BLOCK_DESCRIPTOR_1 1
    struct Block_descriptor_1 {
        uintptr_t reserved;
        uintptr_t size;
    };
    
    // 可选
    #define BLOCK_DESCRIPTOR_2 1
    struct Block_descriptor_2 {
        // requires BLOCK_HAS_COPY_DISPOSE
        BlockCopyFunction copy;
        BlockDisposeFunction dispose;
    };
    
    #define BLOCK_DESCRIPTOR_3 1
    struct Block_descriptor_3 {
        // requires BLOCK_HAS_SIGNATURE
        const char *signature;
        const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
    };
    struct Block_layout {
        void *isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved;
        BlockInvokeFunction invoke;
        struct Block_descriptor_1 *descriptor; //
        // imported variables
    };
    

    Block_layout是Block的底层实现,
    Block_layout中的flags

    第1 位,释放标记,-般常用 BLOCK_NEEDS_FREE 做 位与 操作,一同传入 Flags , 告知该 block 可释放。
    低16位,存储引用计数的值;是一个可选用参数 第24位,低16是否有效的标志,程序根据它来决定是否增加或是减少引用计数位的 值;
    第25位,是否拥有拷贝辅助函数(a copy helper function); 第26位,是否拥有 block 析构函数; 第27位,标志是否有垃圾回收;//OS X 第28位,标志是否是全局block;
    第30位,与 BLOCK_USE_STRET 相对,判断是否当前 block 拥有一个签名。用于 runtime 时动态调用。

    我们在Block_descriptor_3看到了关于签名的内容signature,但是Block_descriptor_3怎么能得到呢
    我们在runtime.cpp看到了如下内容

    #if 0
    static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
    {
        return aBlock->descriptor;
    }
    #endif
    
    static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
    {
        if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
        uint8_t *desc = (uint8_t *)aBlock->descriptor;
        desc += sizeof(struct Block_descriptor_1);
        return (struct Block_descriptor_2 *)desc;
    }
    
    static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
    {
        if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
        uint8_t *desc = (uint8_t *)aBlock->descriptor;
        desc += sizeof(struct Block_descriptor_1);
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
            desc += sizeof(struct Block_descriptor_2);
        }
        return (struct Block_descriptor_3 *)desc;
    }
    

    Block_descriptor_3的读取,先通过flags判断是否存在,然后再通过内存平移得到


    WeChatc302e0e43c7ea2fd4cc095c666b593f7.png

    我们通过拿到的字符串签名,查看[NSMethodSignature signatureWithObjCTypes:"v8@?0"]

    <NSMethodSignature: 0x80bccfb625c2193f>
        number of arguments = 1
        frame size = 224
        is special struct return? NO
        return value: -------- -------- -------- --------
            type encoding (v) 'v'
            flags {}
            modifiers {}
            frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
            memory {offset = 0, size = 0}
        argument 0: -------- -------- -------- --------
            type encoding (@) '@?'
            flags {isObject, isBlock}
            modifiers {}
            frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
            memory {offset = 0, size = 8}
    

    3.Block的三层Copy

    我们分析__block的对象类型

       __block NSString *lg_name = [NSString stringWithFormat:@"cooci"];
            void (^block1)(void) = ^{ // block_copy
                lg_name = @"LG_Cooci";
                NSLog(@"LG_Block - %@",lg_name);
                
                // block 内存
            };
            block1();
    

    经过clang编译后block的结构是

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_lg_name_0 *lg_name; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_lg_name_0 *_lg_name, int flags=0) : lg_name(_lg_name->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    通过clang查看编译后的cpp,再结合block源码
    1.第一层copy。
    我们知道我们创建的Block在引用外部变量的情况下是栈block,但是通过变量持有就变成堆block。因为经过了objc_retainBlock,底层实现_Block_copy。
    因为是从栈到堆,我们只研究栈copy

    void *_Block_copy(const void *arg) {
     struct Block_layout *result =
                (struct Block_layout *)malloc(aBlock->descriptor->size);
            if (!result) return NULL;
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    
            // 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;
    }
    

    我们看到重新malloc一个Block,并且memmove,可知也把lg_namecopy进去了,这是第一次copy
    2.第二层copy
    在_Block_copy中我们看到_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
    }
    

    我们发现了 (*desc->copy)(result, aBlock),在clang下看一下desc的结构

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    

    (*desc->copy)在我们实例中的实现是__main_block_copy_0,

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->lg_name, (void*)src->lg_name, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    

    在源码中查看_Block_object_assign实现,我们只看__blcok 对象的内容

    void _Block_object_assign(void *destArg, const void *object, const int flags) {
        const void **dest = (const void **)destArg;
         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];
             ********/
                
            *dest = _Block_byref_copy(object);
            break;
    }
    

    查看_Block_byref_copy主要代码

     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;
            
            // 问题 - block 内部 持有的 Block_byref 锁持有的对象 是不是同一个
            copy->forwarding = copy; // patch heap copy to point to itself
            src->forwarding = copy;  // patch stack to point to heap copy
            
            copy->size = src->size;
    
            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);
    

    我们看到struct Block_byref *copy = (struct Block_byref *)malloc(src->size);这是第二层copy。
    3.第三层copy
    在_Block_byref_copy中我们看到src2->byref_keep,查看clang下代码

    struct __Block_byref_lg_name_0 {
      void *__isa;
    __Block_byref_lg_name_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);  // 5*8 = 40
     NSString *lg_name;
    };
    

    看源码关于byref的定义

    struct Block_byref {
        void *isa;
        struct Block_byref *forwarding;
        volatile int32_t flags; // contains ref count
        uint32_t size;
    };
    
    struct Block_byref_2 {
        // requires BLOCK_BYREF_HAS_COPY_DISPOSE
        BlockByrefKeepFunction byref_keep; // 结构体 __block  对象
        BlockByrefDestroyFunction byref_destroy;
    };
    

    可知src2->byref_keep,在clang中调用的是__Block_byref_id_object_copy,看到赋值是__Block_byref_id_object_copy_131

    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
        _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    

    _Block_object_assign中

    //flag是131 128+3 为BLOCK_BYREF_CALLER和BLOCK_FIELD_IS_OBJECT
    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_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        
    
            *dest = object;
            break;
    }
    

    完成三层copy。
    4.Block的销毁
    首先调用_Block_release

    void _Block_release(const void *arg) {
        struct Block_layout *aBlock = (struct Block_layout *)arg;
        if (!aBlock) return;
        if (aBlock->flags & BLOCK_IS_GLOBAL) return;
        if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
    
        if (latching_decr_int_should_deallocate(&aBlock->flags)) {
            _Block_call_dispose_helper(aBlock);
            _Block_destructInstance(aBlock);
            free(aBlock);
        }
    }
    static void _Block_call_dispose_helper(struct Block_layout *aBlock)
    {
        struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
        if (!desc) return;
    
        (*desc->dispose)(aBlock);
    }
    

    desc->dispose对应cpp中的__main_block_dispose_0

    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->lg_name, 8/*BLOCK_FIELD_IS_BYREF*/);}
    

    在Block源码_Block_object_dispose中 flags为8 BLOCK_FIELD_IS_BYREF

    void _Block_object_dispose(const void *object, const int flags) {
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
            // get rid of the __block data structure held in a Block
            _Block_byref_release(object);
    break;
    }
    static void _Block_byref_release(const void *arg) {
        struct Block_byref *byref = (struct Block_byref *)arg;
    
        // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
        byref = byref->forwarding;
        
        if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
            int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
            os_assert(refcount);
            if (latching_decr_int_should_deallocate(&byref->flags)) {
                if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                    struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                    (*byref2->byref_destroy)(byref);
                }
                free(byref);
            }
        }
    }
    

    byref2->byref_destroy对应cpp中的__Block_byref_id_object_dispose_131

    static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    

    为什么用会有src + 40我们来看Block_byref结构

    struct __Block_byref_lg_name_0 {
      void *__isa;
    __Block_byref_lg_name_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSString *lg_name;
    };
    

    131等于BLOCK_BYREF_CALLER的128加上BLOCK_FIELD_IS_OBJECT的3
    _Block_object_dispose不做处理。

    _Block_release===》Block 中desc的dispose====〉_Block_object_dispose

    clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk

    clang -rewrite-objc -fobjc-arc -framework Foundation main2.m -o main2.cpp
    Xcrun

    相关文章

      网友评论

        本文标题:iOS-OC底层24:Block底层原理

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