聊一聊Block(二)

2021-03-09  本文已影响0人  晨阳Xia

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

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变量产生强引用

上一篇下一篇

猜你喜欢

热点阅读