IOS开发

Block

2020-10-23  本文已影响0人  越天高

01基本认识

block的原理是怎样的?本质是什么?
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
封装了函数调用以及调用环境的OC对象

__block的作用是什么?有什么使用注意点?

block的属性修饰词为什么是copy?使用block有哪些使用注意?
block一旦没有进行copy操作,就不会在堆上
使用注意:循环引用问题

block在修改NSMutableArray,需不需要添加__block?

  ^{
        NSLog(@"this is a block");

    }();//调用的话在后面加上小括号就可以了

block本质上也是一个OC对象,它内部也有个isa指针

block是封装了函数调用以及函数调用环境的OC对象

block的底层结构如右图所示


block底层结构

我们可以把代码转成cpp文件的代码查看一下block的组成,他第一个变量就是isa指针,说明他本质是一个对象,他也会把外面访问变量在内部也创建一个,封装到block的结构体之中


cpp文件代码


struct __block_impl {
  void *isa;//
  int Flags;
  int Reserved;
  void *FuncPtr;//函数地址,block里面执行的那些代码放到这里
};
 struct __ViewController__viewDidLoad_block_desc_1 {
  size_t reserved;//保留的可能还有其他的用途
  size_t Block_size;//block占据多少内存
 };
struct __ViewController__viewDidLoad_block_impl_1 {
    struct __block_impl impl;
    struct __ViewController__viewDidLoad_block_desc_1* 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;
  }
};
//封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_2f788f_mi_0);

        }
- (void)viewDidLoad {
    [super viewDidLoad];
    ^{
        NSLog(@"this is a block");

    }();

    void (^block)(void) = ^{
        NSLog(@"this is a block");

    };
    struct __ViewController__viewDidLoad_block_impl_1 *block1 =(__bridge struct __ViewController__viewDidLoad_block_impl_1 *)block;
    block();
  //创建一个block。调用了一个结构体的构造函数。返还给我门一个结构体
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//
        //执行block
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

我们可以通过断点去内存查看,这段block的执行的代码的开始地址,就是FuncPtr的存的地址

02底层数据结构

03变量捕获01-auto变量

假如需要参数,重新生成cpp代码

void (^block)(int , int ) = ^(int a, int b)
        {
            NSLog(@"%i", a+b);

        };
        block(10,23);


        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 23);

//更复杂引用外部

int age = 10;
       void(^block)(void) = ^
        {
            NSLog(@"%i", age);//10
        
        };
/*
int age = 10;
       void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
*/
        age = 20;
        block();

//内部实现
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __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;
  }
};


引用外部的变量之后,我们发现block的内部结构发生了改变,多了一个age成员变量,这个变量的值是在编译的时候就传到了block的机构体内部,所以后面的更改不会影响他的值,因为block 执行函数里面的代码时候,使用的是自己的age,而不是外部的age.这就是为什么打印的是10

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_218f06_mi_0, age);

        }

04-变量捕获02-static变量

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制,所谓的捕获就是block内部有个专门的成员来存储那个传进来的值,
默认情况下我们定义出来的变量,前面都有一个auto,所以平时我们都是省略,离开作用域就会销毁。捕获的时候是值传递,
static 局部变量

int age = 10;
        static int height = 10;
       void(^block)(void) = ^
        {
            NSLog(@"%i-%i", age,height);//10-20

        };
/*
 int age = 10;
        static int height = 10;
       void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));//他传进去的是地址
*/
        age = 20;
        height = 20;
        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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_3bdf0b_mi_0, age,(*height));//访问的是指针变量所指向的值。

        }
*/
//内部代码实现
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存的是外面变量的地址值

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_3bdf0b_mi_0, age,(*height));

        }

为什么会有这样的差异,因为auto可能会销毁,age可能在内存中消失,我们以后访问的时候防止他消失我们访问不到数据,static就算是函数执行完了也会存在内存,不用担心他会销毁,所以可以传递地址


block捕获变量关系图

变量捕获03-全局变量

block使用外部的全局变量不会捕获到内部,而是直接使用

void(^block)(void) = ^
             {
                 NSLog(@"%i-%i", _age,height_);//20-20

             };
             _age = 20;
             height_ = 20;
             block();
//
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

                 NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_1ee27a_mi_0, _age,height_);

             }


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

因为他是一个全局变量,在哪里都可以访问他,所以不需要捕获,局部变量需要捕获是因为作用域的问题,可能出现跨函数访问变量,

-(void)test{
    void (^block)(void)=^{
        NSLog(@"%@", self);

    };
    block();
}
//
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);
}

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

说明他会被捕获,说明self是一个局部变量。函数默认有两个参数self 和cmd.
_name可以堪称self->name;说明他也会被捕获,但是捕获的是self对象。而不是单独的对_name捕获。
用self.name,还是要捕获self

上一篇下一篇

猜你喜欢

热点阅读