block变量捕获机制

前言

说下block的变量捕获

修改变量

首先我们看个问题,如果我想修改a的值 都有哪些方法呢
代码

int a = 3;
void (^yang)(void) = ^{
    
    
    NSLog(@"a=%d", a);
};
a = 9;
yang();

方式一: 使用static

// 局部静态变量存储全局区 可以在block内部直接访问修改
static int a = 3;
NSLog(@"%p",&a);
void (^yang)(void) = ^{
    
    
   NSLog(@"a=%d", a);
   NSLog(@"%p",&a);
   a = 12;
};
a = 9;
yang();
NSLog(@"a=%d", a);
2021-11-26 19:04:47.060336+0800 04-捕获变量[29418:37048995] a=9
2021-11-26 19:04:47.060985+0800 04-捕获变量[29418:37048995] a=12

分析

局部静态变量存储全局区 可以在block内部直接访问修改

方式二:使用指针

代码

int a = 3;
int *p = &a;
void (^yang)(void) = ^{
    
    
    NSLog(@"a=%d", a);
    *p = 12;
};
*p = 5;
NSLog(@"a=%d", a);
yang();
NSLog(@"a=%d", a);

输出

2021-11-26 19:09:40.274657+0800 04-捕获变量[30725:37058135] a=5
2021-11-26 19:09:40.275240+0800 04-捕获变量[30725:37058135] a=3
2021-11-26 19:09:40.275341+0800 04-捕获变量[30725:37058135] a=12

方式三:加 __block

代码

__block int a = 3;
NSLog(@"%p", &a);
void (^yang)(void) = ^{
    
    
    NSLog(@"%p", &a);
    NSLog(@"a=%d", a);
};
NSLog(@"%p", &a);
a = 9;
yang();
NSLog(@"%p", &a);

这里有个误区: 很多人认为 加上__block是把a的地址传递给了block。

输出

2021-11-26 19:36:01.983998+0800 04-捕获变量[37788:37107042] 0x7ffee2bf5bc0
2021-11-26 19:36:01.984703+0800 04-捕获变量[37788:37107042] 0x600001b04b78
2021-11-26 19:36:01.984809+0800 04-捕获变量[37788:37107042] 0x600001b04b78
2021-11-26 19:36:01.984910+0800 04-捕获变量[37788:37107042] a=9
2021-11-26 19:36:01.985020+0800 04-捕获变量[37788:37107042] 0x600001b04b78

总结:
1、 如果__block是传递地址 a的地址应该一样
2、 使用static 的方法才是真正的传递地址(看上面的static例子)
3、 block内部能访问a的值是变量捕获机制
4、 能修改a的值是 block变量结构体__block_ref的功劳

变量捕获

变量捕获我们按照基本类型和对象类型来说

基本类型变量

int a = 1;
static int b = 2;
int main(int argc, char * argv[]) {
    
    
    NSString * appDelegateClassName;
    @autoreleasepool {
    
    
        
        int c = 3;
        auto int d = 3;
        static int e = 4;
         
        void (^block)(void) = ^{
    
    
            NSLog(@"a=%d, b=%d, c=%d, d=%d, e=%d", a, b, c, d, e);
        };
       
        a = 9;
        b = 9;
        c = 9;
        d = 9;
        e = 9;
        block();
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

Clang

block结构体结构


int a = 1;
static int b = 2;

struct __main_block_impl_0 {
    
    
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int c;
  int d;
  int *e;
  //__main_block_impl_0 中后面的 : c(_c), d(_d), e(_e) 是干什么用的 
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int _d, int *_e, int flags=0) : c(_c), d(_d), e(_e) {
    
    
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static struct __main_block_desc_0 {
    
    
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = {
    
     0, sizeof(struct __main_block_impl_0)};

block匿名函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
    
  int c = __cself->c; // bound by copy
  int d = __cself->d; // bound by copy
  int *e = __cself->e; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_zm_558cwfjs099fbm2r8kxg8wt00000gt_T_main_416883_mi_0, a, b, c, d, (*e))
         }

block实现和调用

int main(int argc, char * argv[]) {
    
    
    NSString * appDelegateClassName;
    /* @autoreleasepool */ {
    
     __AtAutoreleasePool __autoreleasepool;

        int c = 3;
        auto int d = 3;
        static int e = 4;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, d, &e));

        a = 9;
        b = 9;
        c = 9;
        d = 9;
        e = 9;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

问题

__main_block_impl_0 中后面的 : c(_c), d(_d), e(_e) 是干什么用的

C++允许结构体构造函数后面跟初始化参数列表

void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, d, &e));

struct __main_block_impl_0 {
    
    
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int c;
  int d;
  int *e;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int _d, int *_e, int flags=0) : c(_c), d(_d), e(_e) {
    
    
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

等价于

struct __main_block_impl_0 {
    
    
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int c;
  int d;
  int *e
};

// 创建结构体变量 imp0
struct __main_block_impl_0 imp0;

// 进行成员赋值
_c = c;
imp0.c = _c

_d = d;
imp0.d = _c

_e = e;
imp0.e = _e

输出

// c d 的值没有改变 其他的变量都改变了
2021-11-25 17:16:16.048375+0800 04-捕获变量[83640:34293820] a=9, b=9, c=3, d=3, e=9

语法

函数中可能使用的变量种类:

  • 自动变量(局部变量)
  • 静态变量(静态局部变量)
  • 静态全局变量
  • 全局变量

其中

  • 静态变量
  • 静态全局变量
  • 全局变量

由于存储区域特殊,这其中有三种变量是可以在任何时候以任何状态调用.

  • static修饰的变量为静态变量, static类型的变量(不论是全局还是局部),其值一直保留在内存中,不受大括号的限制,程序结束时才被销毁
  • 所有没有修饰符的局部变量默认的修饰符为auto,auto类型的局部变量的生命周期为离其最近的大括号内,超出大括号,该变量被自动销毁

如图
请添加图片描述

变量传递

__main_block_impl_0 (__main_block_func_0, &__main_block_desc_0_DATA, c, d, &e)
该构造方法 值传递 c d 和 地址传递e

分析

  • a、b为全局变量 c d e 为局部变量
  • c 和 d 保持不变依然为int型变量,e被转换成int型指针变量
  • block结构里中存在 c d e 而不存在 a b 变量 把存在于block结构体中的外部变量称为变量捕获,不存在的则没有被捕获
  • auto变量随着大括号就消失了 捕获地址没用呀 函数栈结束 就没有你的地址了,保存有啥意义。
  • 全局变量有自己独立的存储空间, 一旦生成地址不会修改, main函数可以用 block 也可以拿地址用, 没必要再保存到block内。
  • static修饰的局部变量会被转化成指针变量 讲地址传递给block 所以block 可以修改
  • auto局部变量被捕获,block 拿进来的值 是值传递 。 内部自己也有一个成员变量去接受这个值, 这个变量和外部的变量 是2个地址

对象类型

对象类型的auto变量 在使用 __block 和 __strong 修饰时候
代码等价

__block NSObject *obj3 = [[NSObject alloc] init];
__block __strong NSObject *obj3 = [[NSObject alloc] init];
__block  NSObject *__strong obj3 = [[NSObject alloc] init];
__strong __block NSObject *obj3 = [[NSObject alloc] init];

这个也等价

NSObject *obj1 = [[NSObject alloc] init];
NSObject *__strong obj1 = [[NSObject alloc] init];
__strong NSObject *obj1 = [[NSObject alloc] init];

代码

 __block  int age = 0;
NSObject *obj1 = [[NSObject alloc] init];

__weak NSObject *obj2 = [[NSObject alloc] init];

__block NSObject *obj3 = [[NSObject alloc] init];
// 不能反过来(如:__weak __block),因为__weak只能修饰OC对象
__block __weak NSObject *obj4 = [[NSObject alloc] init];

void (^yang)() = ^{
    
    
   NSLog(@"age==%@",age);
   NSLog(@"obj==%@",obj1);
   NSLog(@"obj==%@",obj2);
   NSLog(@"obj==%@",obj3);
   NSLog(@"obj==%@",obj4);
};

clang 对比代码
请添加图片描述
分析

  • ARC下对象变量默认被 __strong 修饰
  • 加 __block修饰就生成 block变量 (__Block_byref结构体)
  • 捕捉之后 结构体内部对对象的引用是强引用还是弱引用和外面保持一致
    比如:
    obj1 obj3是强引用内部就是__strong
    objc2 objc4 就是弱引用 __weak

__block修改变量

分析下代码报错的原因
代码
请添加图片描述

Clang 对比代码
请添加图片描述)

分析

2 3地址不一样 所以修改的2地址上的a block打印的就不是同一个对象
a 在block内部也不能修改, 提示我们加 __block 关键字

加上 __block

__block int a = 0;
NSLog(@"1==%p",&a);
void (^yang)(void) = ^{
    
    
    NSLog(@"a=%d", a);
    NSLog(@"3==%p",&a);
    a = 13;
};
NSLog(@"2==%p",&a);
a = 9;
yang();
NSLog(@"a=%d", a);
NSLog(@"4==%p",&a);

输出

2021-11-26 18:08:13.839286+0800 04-捕获变量[14289:36949370] 1==0x7ffee78d8bc0
2021-11-26 18:08:13.840071+0800 04-捕获变量[14289:36949370] 2==0x600002b208b8
2021-11-26 18:08:13.840287+0800 04-捕获变量[14289:36949370] a=9
2021-11-26 18:08:13.840414+0800 04-捕获变量[14289:36949370] 3==0x600002b208b8
2021-11-26 18:08:13.840524+0800 04-捕获变量[14289:36949370] a=13
2021-11-26 18:08:13.840647+0800 04-捕获变量[14289:36949370] 4==0x600002b208b8

问题
那么为什么加 __block 就可以修改局部变量的值呢 ?
__block 都干了什么呢?

简单说就是
自动变量加上__block修饰符之后,会改变这个自动变量的存储区域

分析

  • 基本类型变量和对象类型变量都可以被__block 修饰
  • 被__block修改的变量叫block变量
  • __block变量如果是auto类型 会改变对象的存储区域我们看到上面地址输出 从栈到了堆
    后面的赋值操作都是在新的地址上进行的,从地址2 开始 变量a地址就被修改了 后续 block内部和外部修改的地址都是都一个地址

这个堆地址是如何生成的? 我们继续看

底层分析

Step1

__block修饰符 首先告诉编译器 这是一个block变量

Step2

block变量就会生成 __Block_byref结构体

struct __Block_byref_a_0 {
    
    
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

__isa指针 :__Block_byref_a_0中也有isa指针也就是说__Block_byref_a_0本质也一个对象。
__forwarding :__forwarding是__Block_byref_a_0结构体类型的
__flags :0
__size :sizeof(__Block_byref_a_0)即__Block_byref_a_0所占用的内存空间。
a:真正存储变量的地方, 在栈a临死前把它的值给了这里的a ,我们打印的地址 就是这个a的地址 我们修改的a 也是这个a

请添加图片描述)

Step3

block变量a(__Block_byref_a_0) 把外界变量a的值捕获到自己内部
__forwarding存储的值为(__Block_byref_age_0 *)&age,即结构体自己的内存地址。
请添加图片描述

Step4

把自己交给block对象 (__main_block_impl_0) 此时block对象(__main_block_impl_0) 已经持有block变量,开启了一块新的内存 放到了自己的内存布局中。
请添加图片描述

内存布局
请添加图片描述

Step5

我们在block内修改一下a

__block int a = 3;
NSLog(@"%p", &a);
void (^yang)(void) = ^{
    
    
  NSLog(@"%p", &a);
  NSLog(@"a=%d", a);
  a = 13;
  NSLog(@"a=%d", a);
};
NSLog(@"%p", &a);
a = 9;
yang();
NSLog(@"%p", &a);

请添加图片描述
分析

之后我们调用block 触发__main_block_func_0 函数
__main_block_func_0 函数内部 先拿到a结构体对象
通过a结构体拿到__forwarding指针
上面提到过__forwarding中保存的就是__Block_byref_a_0结构体本身,这里也就是a
再通过__forwarding拿到结构体中的a变量并修改其值
经过以上几步block变量的值得到了修改

以上就是捕获变量和修改变量的完整流程


问题

为什么搞这么复杂 中间用一个__forwarding指针? 看我这篇文章: block __forwarding 指针

猜你喜欢

转载自blog.csdn.net/u014641631/article/details/121542560