block(一) block内部实现以及类型

253 阅读4分钟

前言

最近在学习iOS底层的视频 每天看一点,第二天在做一个总结。有错误的地方或者不懂的地方都可以联系我
希望相互帮助。每天进步。
原则上每天都会更新。可以关注我哦

一. block实质

1. block本质

1>.举个例子

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;
}

输出:

Block内部实现原理[96327:13489327] c = 10, d = 20

2>. 内部实现原理

a. 我们将block 转成cpp文件

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. 看着是有点乱 但是把类型强制转换去掉我们在看一下

	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都代表写什么

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
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
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 我们简化一下代码

(__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

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构造函数

  • 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自动释放