Block

作者: 越天高 | 来源:发表于2020-10-23 09:20 被阅读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

    相关文章

      网友评论

        本文标题:Block

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