block的实质

2020-03-20  本文已影响0人  小白PK大牛

1.什么是block

block是将函数及其执行上下文封装起来的对象,是一段代码块,是一个结构体,里面有isa指针指向自己的类(global malloc stack),有desc结构体描述block的信息,__forwarding指向自己或堆上自己的地址,如果block对象截获变量,这些变量也会出现在block结构体中。最重要的block结构体有一个函数指针,指向block代码块。block结构体的构造函数的参数,包括函数指针,描述block的结构体,自动截获的变量(全局变量不用截获),引用到的__block变量。(__block对象也会转变成结构体)

block代码块在编译的时候会生成一个函数,函数第一个参数是前面说到的block对象结构体指针。执行block,相当于执行block里面__forwarding里面的函数指针。

2.什么是block调用

block调用即是函数的调用

3.__block修饰符

一般情况下,对被截获变量进行赋值操作需添加__block修饰符
__block不能修饰全局变量、静态变量(static)

{
   NSMutableArray *array = nil;
   void(^Block)(void) = ^{
           array = [NSMutableArray array];
   }
   Block();
}
是否存在问题?
需要在array声明处添加__block修饰符
__block int multiplier = 6;
    int(^Block)(int) = ^int(int num)
    {
        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d", Block(2));

   结果为8
__block修饰的变量变成了对象
struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

从赋值上看,isa为0,既然有isa指针,那么说明这个结构体也是一个对象,__forwarding存储的是__Block_byref_i_0的地址值,flags为0,size为Block_byref_i_0的内存大小,i是真正存储变量值的地方,是通过__Block_byref_i_0结构体的指针__forwarding读取和修改的变量i.

__block修饰后的底层实现:

1.__block将int i进行包装,包装成一个__Block_byref_i_0结构体对象,结构体中的i是存储i的int值的;
2.当我们在block内修改或访问该对象时,是通过该对象的__forwarding去找对应的结构体再找对应的属性值,这是因为__forwarding在不同情况下指向不同的地址,防止只根据单一的一个内存地址出现变量提前释放无法访问的情况。
那么我们就明白为什么可以修改__block修饰的自动变量了,__block修饰下的i不再是int类型而变成一个对象(对象p),我们block内部访问和修改的是这个对象内部的一个属性,并不是这个对象,所以是可以修改访问的。只不过这个转化为对象的内部过程封装起来不让开发者看到,所以就给人的感觉是可以修改auto变量也就是修改时是int i。

4.block的内存管理

//全局block
_NSConcreteGlobalBlock
//栈block
_NSConcreteStackBlock
//堆block
_NSConcreteMallocBlock
说明.jpeg


5.block的底层结构

通过clang命令将oc代码转换成c++代码(如果遇到_weak的报错是因为_weak是个运行时函数,所以我们需要在clang命令中指定运行时系统版本才能编译):

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp
-(void)viewDidLoad{
    [super viewDidLoad];
    int i = 1;
    void(^block)(void) = ^{
        NSLog(@"%d",i);
    };
    block();
}

转换成c++代码如下:

//block的真实结构体
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  int i;
    //构造函数(相当于OC中的init方法 进行初始化操作) i(_i):将_i的值赋给i flags有默认值,可忽略
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//封存block代码的函数
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  int i = __cself->i; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_3g_7t9fzjm91xxgdq_ysxxghy_80000gn_T_ViewController_c252e7_mi_0,i);
    }

//计算block需要多大的内存
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)};

//viewDidLoad方法
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"));
    //定义的局部变量i
    int i = 1;
    //定义的blcok底部实现
    void(*block)(void) = &__ViewController__viewDidLoad_block_impl_0(
                                            __ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, i));
    //block的调用
    bloc->FuncPtr(block);
}

可看出,定义的block实际上就是一直指向结构体_ViewController_viewDidLoad_block_impl_0的指针(将一个_ViewController_viewDidLoad_block_impl_0结构体的地址赋值给了block变量)。

_ViewController_viewDidLoad_block_impl_0包含以下几个部分:

其中impl包含:

6.循环引用问题

循环引用也是block中一个常见的问题,什么是循环引用呢?
从block捕获对象变量的过程中可看出,block在堆中的时候会根据变量自己的修饰符来进行强引用或者弱引用,假设block对person对象进行强引用,而person如果对block也进行强引用的话,那就形成了循环引用,person对象和block都有强指针指引着,使它们得不到释放。
解决方法:
__weak和__unsafe_unretained
相同点:表示的是对象的一种弱引用关系
不同点:__weak修饰的对象被释放后,指向对象的指针会置空,也就是指向nil,不会产生野指针
__unsafe_unretained修饰的对象被释放后,指针不会置空,而是变成一个野指针,那么此时如果访问这个对象的话,程序就会Crash,抛出BAD_ACCESS的异常。

block可以给NSMutableArray中添加元素吗,需不需要添加__block?

不需要,因为在block块中仅仅是使用了array的内存地址,往内存地址中添加内容,并没有修改arry的内存地址,因此array不需要使用__block修饰也可以正确编译。

blcok为什么能回调声明时的代码块呢?

因为oc调用”block()” 实际就是这句block->FuncPtr(block);,因为blcok->FuncPtr保存的就是__main_block_func_0函数。
总结block声明的时候保存了__main_block_impl_0地址,而__main_block_impl_0则保存了函数体,block的类型,和blcok的结构体大小,最后block回调的时候block->FuncPtr(block)就是调用了__main_block_impl_0中保存的函数__main_block_func_0.

上一篇 下一篇

猜你喜欢

热点阅读