OC底层原理探索-block(下)

本篇文章我们来探索下block的底层原理实现,栈区block是如何拷贝的堆区的,block捕获外部变量的本质,block的数据结构等内容。

block底层实现
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"block探索");
        };
        block();
    }
    return 0;
}

接下来我们clang一下

image.png

  • block是一个结构体,这个结构体定义为__main_block_impl_0,该结构体继承自__block_impl
struct __block_impl {
  void *isa;  //isa指向
  int Flags;
  int Reserved;
  void *FuncPtr;//函数保存
};
  • 在结构体__main_block_impl_0中有个构造函数,该构造函数对block结构体中相关属性进行设置
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
//构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 我们看到构造函数的第一个参数*fp,在构造的时候传入的是__main_block_func_0,其实是函数的实现地址,在定义block时,,将block的任务函数封装到FuncPtr属性中
    image.png
  • 在执行block时,其实是调用block->FuncPtr,然后将block自身作为参数传入
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

【总结】通过上述分析,block其实是个结构体,也可以认为是一个函数。block将任务保存到结构体的FuncPtr属性中,block->FuncPtr(block),block作为隐藏参数,函数执行过程中,会持有block中的全部数据。

捕获变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obj = [[NSObject alloc] init];
        void(^block)(void) = ^{
            NSLog(@"block探索:%@",obj);
        };
        block();
    }
    return 0;
}

重新clang

  • 首先在捕获到外部变量时,__main_block_impl_0构造时传入obj
  • 然后在构造函数中通过值拷贝的方式,对成员变量_obj赋值
  • 在执行任务时,从结构体中取出相应的成员变量进行处理NSObject *obj = __cself->obj;

接下来验证下是否是值copy

image.png

通过打印内外部obj发现,两个obj都指向同一片内存,但是指针地址是不同的,这个时候就可以断定,这里是值拷贝

这里不允许变更obj是因为,block内部有个obj,外部也有,这就会造成歧义,不知道该改变哪个obj

通过__block捕获变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSObject *obj = [NSObject alloc];
        void(^block)(void) = ^{
            
            NSLog(@"block探索:%@",obj);
        };
        block(); 
    }
    return 0;
}

重新clang

  • 这里用__block修饰时,会初始化__Block_byref_a_0的结构体
  • block结构体中多出一个__Block_byref_a_0来声明的属性obj的取地址
  • obj的地址会赋值到__Block_byref_a_0结构体中的__forwarding属性
  • 在调用函数时,取得也是forwarding中的obj,obj->__forwarding->obj

来看下__Block_byref_obj_0结构体

struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

这里我们再来验证下是值拷贝还是指针拷贝

image.png

这里发现内部和外部obj指针地址是一样的,由此可以看出当使用__block修饰时,是进行的指针拷贝

block底层真正类型

我们在代码blcok中打一个断点,并打开Always show Disassembly

image.png

接下来给objc_retainBlock下一个符号断点,来到了_Block_copy
image.png

_Block_copy符号断点,发现是在libsystem_blocks.dylib源码执行
image.png

但是libsystem_blocks.dylib并不开源,我们可以查看libclosure
源码

Block_layout
  struct Block_layout {
        void * __ptrauth_objc_isa_pointer isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved;
        BlockInvokeFunction invoke;
        struct Block_descriptor_1 *descriptor;
        // imported variables
    };
    
    #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
    };

Block_layout包括

  • isa指向的block类型
  • flags标识码
  • reserved保留字段
  • invoke函数,即:FuncPtr
  • descriptor附加信息

先来看下flag定义

enum {
    //释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该block可释放
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    //存储引用引用计数的 值,是一个可选用参数
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    //低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    //是否拥有拷贝辅助函数,(a copy helper function)决定block_description_2
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    //是否拥有block C++析构函数
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    //标志是否有垃圾回收,OSX
    BLOCK_IS_GC =             (1 << 27), // runtime
    //标志是否是全局block
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    //与BLOCK_HAS_SIGNATURE相对,判断是否当前block拥有一个签名,用于runtime时动态调用
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    //是否有签名
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    //使用有拓展,决定block_description_3
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};
  • Block_descriptor_1block是必然存在的reserved是保留字段,sizeblock大小
  • Block_descriptor_2是可选参数,通过Block_layout中的flag来判断,将Block_descriptor_1内存平移的方式来获取对应的值
  • Block_descriptor_3也是可选参数,先平移Block_descriptor_1大小,再判断Block_descriptor_2是否存在,如果存在再平移Block_descriptor_2,不存在则不需要平移Block_descriptor_2
    image.png
block内存变化

引入下面的案例,在block内部访问外部变量

image.png

在经过objc_retainBlock方法事,我们打印寄存器发现为栈block
image.png

在经过_Block_copy方法后,同样打印x0寄存器发现变成了堆block
image.png

【结论】由此得出结论,捕获外部变量在编译时为栈block,运行时通过_Block_copy方法会copy到堆区,变成堆block

_Block_copy分析
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;
    }
    //是否是全局block
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    //这里只能是栈block,因为编译期是不可能申请堆的,这是是将栈copy到堆
    else {// 栈 - 堆 (编译期)
        // Its a stack block.  Make a copy.
        //获取大小
        size_t size = Block_size(aBlock);
        //开辟空间
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        if (!result) return NULL;
        //进行block copy
        memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        // invoke 赋值
        result->invoke = aBlock->invoke;

#if __has_feature(ptrauth_signed_block_descriptors)
        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
            uintptr_t oldDesc = ptrauth_blend_discriminator(
                    &aBlock->descriptor,
                    _Block_descriptor_ptrauth_discriminator);
            uintptr_t newDesc = ptrauth_blend_discriminator(
                    &result->descriptor,
                    _Block_descriptor_ptrauth_discriminator);

            result->descriptor =
                    ptrauth_auth_and_resign(aBlock->descriptor,
                                            ptrauth_key_asda, oldDesc,
                                            ptrauth_key_asda, newDesc);
        }
#endif
#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.
        //修改isa指向堆
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}
  • 这里现将block强转成Block_layout类型
  • 判断是否正在释放,如果是则不处理
  • 判断是否是全局block,如果是则不处理
  • 接下来else判断,在编译期不可能是堆block,所以这里一定是栈block
  • 获取block大小
  • 开辟内存空间,通过memmove进行数据拷贝,并将invoke 和 flag赋值
  • 最后将isa指向堆
Block_byref结构体拷贝

回到main.cpp文件中,查看__main_block_desc_0结构体定义

image.png

其中size和reservedBlock_descriptor_1的两个属性。void (*copy)和void (*dispose)对应的是Block_descriptor_2两方法。在copy方法中会调用_Block_object_assign来查看下该方法
image.png

先看下注释

  • BLOCK_FIELD_IS_OBJECT:捕获OC对象
  • BLOCK_FIELD_IS_BLOCK :捕获另一个block
  • BLOCK_FIELD_IS_BYREF : 捕获用__block声明的变量
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:
        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        *dest = _Block_copy(object);
        break;

      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        *dest = _Block_byref_copy(object);
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        *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:
        *dest = object;
        break;

      default:
        break;
    }
}

  • 如果是BLOCK_FIELD_IS_OBJECT,指针指向该对象,将对该对象进行持有,引用计数加1
  • BLOCK_FIELD_IS_BLOCK,捕获一个block,则进行_Block_copy操作
  • BLOCK_FIELD_IS_BYREF:用__block声明,则调用_Block_byref_copy

_Block_byref_copy源码

static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    // __block 内存是一样 同一个家伙
    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;
        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);
        }
        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) {
        latching_incr_int(&src->forwarding->flags);
    }
    return src->forwarding;
}
  • 这里是将外部对象封装成Block_byref *src
  • 然后去malloc一个Block_byref *copy
  • 设置 forwarding 保证内外部Block_byref对象指向同一个对象,这也是为什么__block修饰的对象具有修改能力的原因
copy->forwarding = copy; 
src->forwarding = copy; 
  • 拷贝等操作完成以后,调用了byref_keep函数,这个函数又干了些啥事情
Block_byref 中的 object 拷贝分析

先源码看下 Block_byref相关结构:
// 结构体
struct Block_byref {
void * __ptrauth_objc_isa_pointer isa; // 8
struct Block_byref *forwarding; // 8
volatile int32_t flags; // contains ref count//4
uint32_t size; // 4
};

struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; // 8
BlockByrefDestroyFunction byref_destroy; // 8
};

struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};

__block底层是__Block_byref_obj_0结构体,其声明的对象,我们可以看到void (*__Block_byref_id_object_copy)(void*, void*)这个属性其实就是byref_keep变量,传入的是__Block_byref_id_object_copy_131
这里byref_keep调用__Block_byref_id_object_copy_131

image.png

struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

看下__Block_byref_id_object_copy_131,我们发现最终还是进入_Block_object_assign

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
  • 结合__Block_byref_obj_0结构和Block_byref结构,向下平移40个字节,得到的是NSObject *obj,对obj也是调用了_Block_object_assign函数,传入的flags是131
    image.png
  • 也就是堆中object和栈中object指向同一片内存空间。

三层拷贝总结

  • block通过_Block_copy函数从栈拷贝到堆上。
  • block捕获变量Block_byref,通过_Block_object_assign函数从栈拷贝到堆上。
  • Block_byref中的object也是通过_Block_object_assign函数进行相关拷贝。

注意:只有用__block声明的对象才会有三层拷贝

block释放

释放时调用的__Block_byref_id_object_dispose_131函数:

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

_Block_object_dispose

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;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}


`

你可能感兴趣的:(OC底层原理探索-block(下))