美文网首页
Block如何捕获外部变量一:基本数据类型

Block如何捕获外部变量一:基本数据类型

作者: 小心韩国人 | 来源:发表于2018-08-29 18:16 被阅读63次

上一篇我们介绍了Block的本质(想要了解,点击这里传送门),这一篇,我们详细讲解Block捕获外部变量的机制.我们把block捕获外部变量的情况分为以下几种,见下图:

一:auto变量
  • auto变量:自动变量,离开作用域就会销毁,一般我们创建的局部变量都是auto变量,比如 int age = 10,系统会在默认在前面加上auto int age = 10
    首先我们要搞清楚,什么是捕获,所谓捕获外部变量,意思就是在block内部,创建一个变量来存放外部变量,这就叫做捕获.先做一个小小的Test:
{
        int age = 10;
        void (^block)(void) = ^{
            NSLog(@"age is %d",age);
        };
        age = 20;
        block();
    }

定义一个age变量,在block内部访问这个age,在调用block之前,修改这个age值,那么最后输出的age是多少呢?很简单,输出的age还是10,相信很多人都知道结果,我们从底层来看一下为什么会这样.
上面代码通过Clang编译器转换后如下:



{
        int age = 10;
       //定义block
        void (*block)(void) = &__main_block_impl_0(
                                                   __main_block_func_0,
                                                   &__main_block_desc_0_DATA,
                                                   age
                                                   );
        age = 20;
       //调用block
        block->FuncPtr(block);
    return 0;
}

我们看到在调用block的构造函数时,传入了三个参数,分别是:__main_block_func_0,&__main_block_desc_0_DATA,age,我们找到block的构造函数,看看内部如何处理这个age:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;  // 1: 定义了一个同名的age变量
//block构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    age = _age; //2 :C++的特殊语法,在构造函数内部会默认把_age赋值给age
  }
};

通过查看block的内部结构看我们发现,block内部创建了一个age变量,并且在block构造函数中,把传递进来的_age赋值给了这个age变量.我们看看调用block时,他的底部取的是哪个age:

//调用block的FuncPtr函数,把block当做参数传递进去
block->FuncPtr(block);
//FuncPtr函数内部
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//通过传递的block,找到block内部的age
  int age = __cself->age; // bound by copy
//打印age
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_5t_pxd6sp5x6rl9gnk21q2q934h0000gn_T_main_3089d7_mi_0,age);
        }

通过底层代码,我们看到,在调用block时,block会找到自己内部的age变量,然后打印数出,所以我们修改age = 20,并不会影响block内部的age

二:static变量

我们把上面的代码稍作修改:

auto int age = 10;
        static int height = 20;
        void (^block)(void) = ^{
            NSLog(@"age is %d, height is %d",age,height);
        };
        age = 20;
        height = 20;
        block();

打印的结果是age is 10, height is 20
同样转换 C++ 代码,查看底层:

{
        auto int age = 10;
        static int height = 20;
        void (*block)(void) = &__main_block_impl_0(
                                                   __main_block_func_0,
                                                   &__main_block_desc_0_DATA,
                                                   age,
                                                   &height //传递指针
                                                   );
        age = 20;
        height = 20;
        (block->FuncPtr(block);
    }

我们看到,在定义block时,调用block的构造函数,传递参数时,age传递的是值,而height传递的是指针,看看构造函数内部:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;//定义 age 变量
  int *height;//定义一个 指针变量,存放外部变量的指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

我们看到,在block内部,定义了两个变量age,height,不同的是,height是一个指针指针变量,用于存放外部变量的指针.我们再来看看执行block代码块的内部:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *height = __cself->height; // bound by copy
 // *height : 取出指针变量所指向的内存的值
   NSLog((NSString *)&__NSConstantStringImpl__var_folders_5t_pxd6sp5x6rl9gnk21q2q934h0000gn_T_main_bf6cae_mi_0,age,(*height));
        }

我们看到,对于age是捕获到内部,把外部age的值存起来,而对于height,是把外部变量的指针保存起来,所以,我们在修改height时,会影响到block内部的值

思考:为什么会出现这两种情况?
原因很简单,因为auto是自动变量,出了作用域后会自动销毁的,如果我们保留他的指针,就会存在访问野指针的情况

//定义block类型
void(^block)(void);

void test(){
    int age = 10;
    static int height = 20;
//在block内部访问 age , height
    block = ^{
        NSLog(@"age is %d, height is %d",age,height);
    };
    age = 20;
    height = 20;
}

//在main函数中调用
int main(int argc, const char * argv[]) {
        test();
 //test调用后,age变量就会自动销毁,如果block内部是保留age变量的指针,那么我们在调用block()时,就出现访问野指针
        block();
}
三:全局变量

全局变量哪里都可以访问,所以block内部是不会捕获全局变量的,直接访问,这个很好理解,我们直接看代码:

全局变量底层

为什么全局变量不需要捕获?
因为全局变量无论哪个函数都可以访问,block内部当然也可以正常访问,所以根部无需捕获
为什么局部变量就需要捕获呢?
因为作用域的问题,我们在一个函数中定义变量,在block内部访问,本质上跨函数访问,所以需要捕获起来.

  • Test1:
    我们在Person类中写一个test()方法,在test()方法中定义一个block并访问self,请问block会不会捕获self.
@implementation Person

- (void)test{
    void(^block)(void) = ^{
        NSLog(@"会不会捕获self--%@",self);
    };
    block();
}

@end

答案是会捕获self,我们看看底层代码:

struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

很显然block内部的确声明了一个Person *self用于保存self,既然block内部捕获了self,那就说明self肯定是一个局部变量.那问题就来了,为什么self会是一个局部变量?它应该是一个全局变量呀?我们看一下转换后的test()方法:

static void _I_Person_test(Person * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

我们 OC 中的test()方法时没有参数的,但是转换成 C++ 后就多了两个参数self,_cmd,其实我们每个 OC 方法都会默认有这两个参数,这也是为什么我们在每个方法中都能访问self_cmd,而参数就是局部变量,所以block就自然而然的捕获了self.

  • Test2:
    我们对 Test1 稍加修改,增加一个name属性,然后在block中访问_name,这时候block会捕获self吗?
    答案是:会.继续看一下底层:
访问_name
我们看到,block底层仍然捕获了self,这是因为,我们去访问_name属性的时候,实际上相当于self -> name,要想获取name,必须要先获取self,因为name是从self中来的,所以block内部会对self进行捕获.
总结:

一:只要是局部变量,不管是auto 变量,还是static 变量,block都会捕获.不同的是,对于auto 变量,block是保存值,而static 变量 是保存的指针.
二:如果是全局变量,根本不需要捕获,直接访问
本篇只是讲解了block捕获基本数据类型的auto变量,下一篇会讲解block捕获对象类型的auto变量

相关文章

  • 06.4-OC中block捕获对象类型的变量

    block捕获对象类型的变量 前面讲解的block变量捕获,我们讲解了block捕获基本数据类型的情况,下面我们再...

  • Block

    Block Block 可以捕获外部变量 Block 可以捕获来自外部作用域的变量,这是Block一个很强大的特性...

  • oc中block底层原理分析(二)

    一、block对对象变量的捕获 block一般使用过程中都是对对象变量的捕获,那么对象变量的捕获同基本数据类型变量...

  • block

    block block内部访问外部的变量 如果外部的变量是基本数据类型,在block内部不可以直接修改外部变量的值...

  • iOS中的Block详解2(附面试题) - 底层原理总结

    block对对象变量的捕获 block一般使用过程中都是对对象变量的捕获,那么对象变量的捕获和基本数据类型变量相同...

  • Block变量捕获详解(一)

    什么是Block变量捕获block变量捕获就是在block内部创建一个变量来存放外部变量什么是值捕获block将外...

  • Block 之 变量捕获

    为了保证block内部能够正常访问外部的变量,block有个变量捕获机制,即捕获外部变量。 前言: 搞清成员变量、...

  • iOS Block是如何引用外部变量的?

    1. block如何引用外部基本数据类型变量? block内引用基本数据类型,会直接将基本数据类型在内存中复制出另...

  • OC基础-Block(2)

    OC基础-Block(2)Block的变量捕获为了保证block内部能够正常访问外部的变量,block有个变量捕获...

  • OC中的Block(二)

    block的变量捕获(capture) 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制 ...

网友评论

      本文标题:Block如何捕获外部变量一:基本数据类型

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