美文网首页
聊一聊Block(二)

聊一聊Block(二)

作者: 晨阳Xia | 来源:发表于2021-03-09 16:26 被阅读0次

    函数内定义的变量叫做局部变量,函数外部定义的变量叫做全局变量。

    block封装了函数调用,以及函数调用环境的oc对象

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        int age = 10;
        void (^blockName)(void) = ^{
            NSLog(@"%d",age);
        };
        blockName();
        
    }
    

    源码如下:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    static struct __ViewController__viewDidLoad_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
    
    struct __ViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController__viewDidLoad_block_desc_0* Desc;
      int age;
      __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
      int age = __cself->age; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_zs_r4g726_d3rd7y0hvw0_7ckym0000gn_T_ViewController_3bd104_mi_0,age);
        }
    
    
    static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
        ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
        int age = 10;
        void (*blockName)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, age));
        ((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
    
    }
    
    image.png

    自动变量截获值不截获指针,是因为自动变量随时可能销毁,如果解惑指针,有可能会在访问自动变量的时候指针已经销毁
    代码举例:

    void (^block)(void);
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        test(); 
        block();// 作用域外age已经被销毁,所以如果age是指针传递,则此时age的地址已经被销毁,会造成坏内存访问
    }
    
    void test() {
        int age = 10;
        static int height = 110;
        block = ^{
            NSLog(@"%d, %d", age, height);
        };
        age = 10; 
        height = 120;
    }
    

    如何证明self是一个局部变量

    void test () {
         void (^blockName)(void) = ^{
            NSLog(@"%@",self);
        };
        blockName();
    }
    
    编译之后的代码:
    struct __main_block_impl_0 {
          struct __block_impl impl;
          struct __main__block_desc_0;
          XSYPerson *self; // 会被捕获,所以说明self是局部变量
    }
    
    test的未省略方法为:
    void test(XSYPerson *self ,SEL _cmd)
     {
        void (^blockName)(void) = ^{
            NSLog(@"%@",self);
        };
        blockName();
     }
    

    能被block捕获的变量都是局部变量

    全局变量不会捕获,局部变量会捕获。

    void test () {
         void (^blockName)(void) = ^{
            NSLog(@"%@",_name );
        };
        blockName();
    }
    
    编译之后的代码:
    
    struct __main_block_impl_0 {
          struct __block_impl impl;
          struct __main__block_desc_0;
          XSYPerson *self; // 依然捕获的是self对象
    }
    因为
    _name 是 self->_name这样来访问的
    

    block的内存布局

    image.png image.png

    isa决定了属于哪个类型的block

    为什么说block是对象

    1. superclass最终是nsobject
    2. c++编译成功是一个带有isa的结构体,这跟nsobject的结构相同

    block的类型

    image.png

    在arc环境下,一下情况会自动调用copy将blockcopy到堆上

    1. block作为函数返回时
    2. 将block赋值给__strong指针时
    3. block作为Cocoa API中方法名含有usingBlock的方法参数时
    4. block作为GCDAPI的方法参数时
      拷贝到堆上,就是为了防止栈block被销毁,从而引发坏内存访问。
    typedef void (^XSYBlock)(void);
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        XSYBlock block;
    
        {
            XSYPerson *person = [[XSYPerson alloc] init];
            block = ^{
            // 此处堆person有一个retain的操作。
                NSLog(@"%@",person);
            };
        }
        block();
        
    }
    
    // person不会被释放的原因:
    因为person是自动变量,所以会被block捕获,又因为是对象,则连通修饰符一起被捕获,所以person的引用计数加1。所以在块外部,因为block没有释放,所以person也不会被释放。
    

    如果block是在栈上,block内部不会对person产生强引用。

        ^{
            // 此处堆person有一个retain的操作。
                NSLog(@"%@",person);
            };
    此处block对person没有强引用,即便person自己是强引用。
    
    解释:block自己都保不住自己的命,person更保不住。所以无需强引用。
    

    block copy的操作流程

    如果block被拷贝到堆上:
    会调用block内部的copy函数,
    copy函数会调用_Block_object_assign函数
    _block_object_assign函数会根据auto变量的修饰符(__strong, __weak, __unsafe_retain)做出相应的操作,类似于retain(强引用,弱引用)
    如果block从堆上移除:
    会调用block内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放引用的auto变量,类似于release


    image.png

    __block

    不能修饰可以解决block内部无法修改auto变量的问题
    __block不能修饰全局变量和静态变量。
    编译器将__block变量包装称为一个对象。


    image.png image.png

    __block修饰的自动变量的本质

    __block int age = 10;
        void (^arrBlockName)(void) = ^ {
            age = 11;
        };
    arrBlockName();
    NSLog(@"%d",age);
    输出:11
    
    struct __Block_byref_age_0 {
        void *isa;
        __Block_byref_age_0 *__forwarding;
        int flags;
        int __size;
        int age;
    }
    
    struct __main_block_impl_1 {
        void *isa;
        struct _block_impl *impl;
        struct __main_block_desc_0 *Desc;
        struct __Block_byref_age_0 *age;
    }
    

    __block的内存管理

    __block int age = 10;
    void (^arrBlockName)(void) = ^ {
        age = 11;
    };
    

    因为__block对象是自动变量,所以在栈上
    block被强引用修饰,被拷贝到堆上
    这个时候,__block也会被拷贝到堆上,block内部堆__block对象是强引用,引用计数加一。
    当block时放的时候,__block会通过dispose函数销毁

    当block使用__block对象和NSObject对象的区别

    相同点:

    1. 当block在栈上,block对两种对象都不会产生强引用
    2. 两个对象都是通过copy方法拷贝到堆上
    3. 两个对象都是通过dispost方法销毁
      不同点:
    4. NSObject对象会通过修饰符来进行强引用或弱引用
    5. block对 __block对象只存在强引用

    当__block变量在栈上时,不会对__block变量产生强引用

    • 当__block变量被copy到堆时,
      会调用__block变量内部的copy函数,
      copy函数会调用_Block_object_assign函数
      _Block_object_assign会根据所指向的对象的修饰符(__strong,__weak, __unsafe_unretain)做出相应的操作,形成强引用或弱引用(注意:这里仅限于ARC时retain,MRC时不会retain)
      • 如果__block变量从堆上移除
        会调用__block变量内部的dispose函数
        dispose函数内部会调用_Block_object_dispose函数
        _Block_object_dispose函数会自动释放指向的对象(release)

    相关文章

      网友评论

          本文标题:聊一聊Block(二)

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