block内部实现

2019-09-27 19:30:23 浏览数 (1)

一. block实质

1. block本质
1>.举个例子
代码语言:javascript复制
int main(int argc, const char * argv[]) {    @autoreleasepool {        // insert code here...        int c = 10;        static int d = 10;        void (^testBlock)(int a, NSString *b) = ^(int a, NSString * b) {            NSLog(@"c = %d, d = %d", c, d);        };        d = 20;        testBlock(30,@"40");    }    return 0;}

输出:

代码语言:javascript复制
Block内部实现原理[96327:13489327] c = 10, d = 20
2>. 内部实现原理

a. 我们将block 转成cpp文件

代码语言:javascript复制
int main(int argc, const char * argv[]) {    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;        int c = 10;        static int d = 10;        void (*testBlock)(int a, NSString *b) = ((void (*)(int, NSString *))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, &d));        d = 20;        ((void (*)(__block_impl *, int, NSString *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30, (NSString *)&__NSConstantStringImpl__var_folders__9_b_xp2qr12j348_dq41f7zqqh0000gn_T_main_d0fe91_mi_1);    }    return 0;}

b. 看着是有点乱 但是把类型强制转换去掉我们在看一下

代码语言:javascript复制
  int main(int argc, const char * argv[]) {        int c = 10;        static int d = 10;        // 定义block  生成__main_block_impl_0结构体 并传入两个参数        // 1.block方法        // 2.blcok信息        void (*testBlock)(int a, NSString *b) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, c, &d));            d = 20;            // testBLock->FuncPtr   参数为testblock 30 和字符串        ((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30, (NSString *)&__NSConstantStringImpl__var_folders__9_b_xp2qr12j348_dq41f7zqqh0000gn_T_main_d0fe91_mi_1);    }    return 0;}

c. 我们先看一下__ main_block_impl_0结构体和 __ main_block_func_0 还有__main_block_desc_0_DATA都代表写什么

代码语言:javascript复制
struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;    // c 和 d 是block捕捉(capture)进来的。如果block没有引用,则不会出现c和d  // 具体什么情况会捕捉,文章后面会讲解。同学可以去验证下  // 没有修饰的为值传递  int c;  // static 修饰的为地址传递  int *d;  // 构造函数    c(_c), d(_d) 直接_d 给d赋值 直接_c 给c赋值  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_d, int flags=0) : c(_c), d(_d) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};

d. 通过这个 我们可以知道 d为地址传递 c为值传递。所以当d重新赋值后, 因为d传递的是d变量的地址。所以值会跟着改变,而c为值传递 所以当c改变的时候 block结构体中并不会跟着改变。

我们继续往下看传入的参数main_block_func_0和__main_block_desc_0_DATA

  • main_block_func_0
代码语言:javascript复制
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, NSString *b) {  // _cself 为结构体本身  获取c 和 *d  int c = __cself->c; // bound by copy  int *d = __cself->d; // bound by copy  NSLog((NSString *)&__NSConstantStringImpl__var_folders__9_b_xp2qr12j348_dq41f7zqqh0000gn_T_main_d0fe91_mi_0, c,(*d));}
  • __main_block_desc_0_DATA
代码语言:javascript复制
static struct __main_block_desc_0 {  size_t reserved;  //__main_block_impl_0的大小  size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

e. 我们继续往下看 我们如何调用block 我们简化一下代码

代码语言:javascript复制
(__block_impl *)testBlock)->FuncPtr((__block_impl *)testBlock, 30, (NSString *)&__NSConstantStringImpl__var_folders__9_b_xp2qr12j348_dq41f7zqqh0000gn_T_main_d0fe91_mi_1);    

通过testBlock强制转换为__block_impl * 去调用FuncPtr 然后传入参数testBlock,30和字符串

我们可以看到定义的时候 testBlock 为&__main_block_impl_0

从下图构造函数 我们可以知道 传入的block方法给了impl的funcptr。funcptr也就是上面的main_block_func_0

代码语言:javascript复制
struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  // 构造函数    c(_c), d(_d) 直接_d 给d赋值 直接_c 给c赋值  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_d, int flags=0) : c(_c), d(_d) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};

:为什么testBlock可以直接调用impl中的FuncPtr呢 ?

答: 因为impl为__ main_block_impl_0中的第一个结构体.在内存中是头指针是指向的一个地址 所以可以转换。

3>. 结论

通过上述的源码 我们可以知道了结构体的大致结构。以及调用方式 下图看起来可能更直观

  • block构造
  • block调用
2. block capture

变量类型

是否可以被捕捉

访问方式

auto(默认为auto) int a == auto int a

值传递

static

地址传递

全局变量

不需要捕获

直接访问

二. block 类型

block分为以下三个类型
  • __ NSGlobalBlock __
  • __ NSStackBlock __
  • __ NSMallocBlock __
继承关系

__ NSMallocBlock __ , __ NSMallocBlock , NSBlock, NSObject

__ NSStackBlock __ , __ NSStackBlock , NSBlock, NSObject

__ NSGlobalBlock __ , __ NSGlobalBlock __ , NSBlock, NSObject

如何分配

__ NSGlobalBlock __

没有访问auto对象

__ NSStackBlock __

访问了auto对象

__ NSMallocBlock __

__ NSStacBlock __ 调用了copy

关闭arc验证

在ARC上

__ NSStackBlock __ 在进行copy 会copy到 __ NSMallocBlock __区

目的是为了防止block自动释放

如有错误请指。相互交流学习。

0 人点赞