Block本质和类型

2021-03-24  本文已影响0人  lth123

一.Block的本质


  • block在本质上也是一个oc对象 ,因为他内部有一个isa指针
  • block封装了函数调用以及函数调用的环境
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 1.定义了一个block变量
        void(^block)(void) = ^{
            NSLog(@"Hello, World!");
        };
        
        //2. 指向block
        block();
    }
    return 0;
}

使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 命令将main.m 转换成main.cpp,可以看到main函数的的c++实现

// main函数
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        void(*block)(void) = ((void (*)())&__main_block_impl_0(
                                                               (void *)__main_block_func_0,
                                                               &__main_block_desc_0_DATA)
                                                              );
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
1.定义block的c++实现
 void(*block)(void) = ((void (*)())&__main_block_impl_0(
                                                        (void *)__main_block_func_0,
                                                         &__main_block_desc_0_DATA)
                                                        );

执行了__main_block_impl_0这个函数,并且传入了(void )__main_block_func_0和&__main_block_desc_0_DATA两个参数,将__main_block_impl_0这个构造函数的返回值的地址赋值给了block指针变量,block一个指向了类型是__main_block_impl_0的指针

我们看下__main_block_impl_0这个函数的具体实现是什么样的
__main_block_impl_0函数实现

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; //记录结构体大小的成员变量
    // c++ 语法,构造函数,返回结构体对象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp; //指向__main_block_func_0,也就是block封装的代码
    Desc = desc;
  }
};

可以看到__main_block_impl_0是一个结构体,他有两个成员变量和一个构造方法

第一个成员变量impl的结构体

// 定义了一个impl的结构体
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr; // 指向block封装的代码块
};

在这里我们看到了isa指针,说明__block_impl的本质是一个oc对象,而__block_impl又是__main_block_impl_0的成员变量,所以__main_block_impl_0的本质也是一个oc对象。在上面我们说道,block指向了__main_block_impl_0这个结构体,所以说block本质上是指向一个oc对象的指针

第二个成员变量Desc的结构体

// 定义了一个描述block大小的结构体
static struct __main_block_desc_0 {
  size_t reserved; // 0
  size_t Block_size; //__main_block_impl_0 的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

构造方法的具体实现

 // c++ 语法,构造函数,返回结构体对象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp; //指向__main_block_func_0,也就是block封装的代码
    Desc = desc;
  }

__main_block_impl_0构造方法需要三个参数,fp赋值给了impl.FuncPtr,desc赋值给了Desc, flags 默认等于0,
fp就是定义block时传入的(void *)__main_block_func_0
desc就是定义block时传入的&__main_block_desc_0_DATA)

参数1:__main_block_func_0的实现如下:

//封装了block内部需要执行的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_6a2183_mi_0);
}

static __NSConstantStringImpl __NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_6a2183_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Hello, World!",13};

__main_block_func_0这个函数就是对应NSLog(@"Hello, World!");,__main_block_func_0函数就是block封装的代码块

参数2:__main_block_desc_0_DATA

// 定义了一个描述block大小的结构体
static struct __main_block_desc_0 {
  size_t reserved; // 0
  size_t Block_size; //__main_block_impl_0 的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

__main_block_desc_0_DATA主要是记录 __main_block_impl_0这个结构体的大小

定义block过程总结:

1.首先执行__main_block_impl_0这个构造方法,传入__main_block_func_0和__main_block_desc_0_DATA两个参数。参数1记录了block代码的具体实现,参数2记录了__main_block_impl_0这个结构体的size;

2. __main_block_impl_0将__main_block_func_0赋值给impl.FuncPtr,将__main_block_impl_0赋值给Desc;

3.__main_block_impl_0初始化成功后,会返回一个__main_block_impl_0类型的结构体,并且将返回值的地址复制给block。

2.如何执行block
 ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

block变量就是指向__main_block_impl_0的地址,通过block变量找到__main_block_impl_0成员变量里面的FuncPtr,在定义block的时候,将block封装的代码块的地址赋值给了FuncPtr,只需要执行函数FuncPtr().

((__block_impl )block) 强制类型转换,强制将block的类型转换成__block_impl,所以才能反问到FuncPtr

上面的block是最简单的block,没有参数,没有返回值,也没有访问外部的变量

二.Block传入参数

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(int, int) = ^(int a,int b){
            NSLog(@"%d--%d",a,b);
        };
        
        block(5,10);
    }
    return 0;
}

// 对应的c++实现

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        void(*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));


        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 5, 10);
    }
    return 0;
}

1.((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 5, 10);可以看到在执行block的时候传入了5和10,

2.封装block具体实现也多了两个参数,a和b
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_9045fc_mi_0,a,b);
}

二.Block访问自动变量(没用static修饰的局部变量,离开作用域会销毁)

自动变量会被block捕获,访问方式是值传递;修改block外面的值不会改变block捕获的值

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        void(^block)(void) = ^(){
            NSLog(@"%d",a);
        };
        a = 20;
        block();
    }
    return 0;
}
输出结果是10

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        // 函数是值传递,在定义block的时候传入的是10
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        // 修改自动变量a的值为20,不会影响block内部的成员变量a
        a = 20;
        // 打印成员变量a的值10
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

1.在执行__main_block_impl_0函数的时候多传了一个参数a,此时a的值是10;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; // 保存捕获的值10
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
2.__main_block_impl_0结构体里面多了一个成员变量a,用来保存在初始化传入的10;

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  // 获取block成员变量a的值10
  int a = __cself->a; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_707356_mi_0,a);
}
3.在执行block的时候,获取的是__main_block_impl_0内部的成员变量a,而不是外面的自动变量a,所以打印的是10

三.Block访问static修饰的局部变量

局部变量会被block捕获,访问方式是地址传递;修改block外面的值会改变block捕获的值

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        static int b = 10;
         block = ^(){
            NSLog(@"%d--%d",a,b);
        };
        a = 20;
        b = 20;
    }
    return 0;
}

输出结果: 10,20

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        // 自动变量a
        int a = 10;
        // static 修改是局部变量b
        static int b = 10;
        
        // 执行__main_block_impl_0,传入 a的值和 b的地址
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));
        a = 20;
        b = 20;
        // 执行block
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; //保存a的值
  int *b; // 保存b的地址
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  // 获取a的值10
  int a = __cself->a; // bound by copy
  // 获取b的地址存储的内容 20
  int *b = __cself->b; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_ed5e58_mi_0,a,(*b));
}

- 捕获外面的局部变量时,分别捕获的是a的值10,b的地址&b;
- 将捕获的10和&b分别保存到__main_block_impl_0结构体的a和b中;
- 执行block时,取出a的值10和指针b指向的地址所存储的内容20;

为什么自动变量是值传递,而静态局部变量是地址传递?

static的作用:

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
根本原因是自动变量出了作用域会被释放,而静态局部变量不会被释放,而block的执行时机是不确定的,如果自动变量也是地址传递,在执行block的时候,自动变量有可能被释放,当时传入的地址是不可用的,会出现坏的内存访问。

void(^block)(void);
void testBlock(){
    int a = 10;
    static int b = 10;
     block = ^(){
        NSLog(@"%d--%d",a,b);
    };
    a = 20;
    b = 20;

}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        testBlock();
        block();
    }
    return 0;
}

testBlock()执行完成后,自动变量a的已经被释放了,而b不会被释放;在执行block()的时候,如果访问a的地址,会出现坏内存访问


四.Block访问全局变量 static修饰的全局变量

Block访问全局变量和 static修饰的全局变量,不会捕获


int a = 10;
static int b = 10;

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_46ed42_mi_0,a,b);
}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        a = 20;
        b = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

可以看到a和b定义在全局区,__main_block_impl_0里面并没有成员变量去保存a和b
在执行__main_block_func_0函数的时候,是直接访问a和b。

为什么访问局部变量会捕获,访问全局变量不会捕获

作用域问题:
局部变量只能在函数内部访问,在别的函数中是无法访问另外一个函数定义的局部变量的。
block的实现是在__main_block_func_0函数中,变量定义在其他的函数中,在__main_block_func_0中是无法访问他的函数中定的变量的,所以需要捕获;而全局变量,在任何地方都能访问,所以不需要捕获就能访问


image.png

五.Block的类型

block有三种类型,可以通过class方法或者 isa看具体类型,最终都集成自NSBlock

  1. NSGlobalBlock
  2. NSStackBlock
  3. NSMallocBlock

三种类型的block的内存分配情况

image.png image.png

栈上的block是系统自动分配内存,自动回收内存,堆上的block由开发者申请内存,释放内存
如果是栈上的block捕获了局部变量,在其他地方调用时,会出现意向不到的结果

void (^block)(void);

void testBlock(){
    int a = 10;
    block = ^{
        NSLog(@"a的值是:%d",a);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        testBlock();
        block();
    }
    return 0;
}

a的值是随机的,并不是10,这是因为 ^{NSLog(@"a的值是:%d",a);} 在栈上,当testBlock函数执行完毕后,内存会被回收,全局变量block指向的内存区域的数据不再是当时的数据.

void (^block)(void);

void testBlock(){
    int a = 10;
    block = [^{
        NSLog(@"a的值是:%d",a);
    } copy];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        testBlock();
        block();
    }
    return 0;
}

a的值是10

NSStackBlock执行copy操作后,会将栈上的block拷贝到堆上, 变成NSMallockBlock,在堆上,内存由开发者释放,这个时候block全局变量指向堆上的block,block不会被释放

在ARC环境下,以下情况,栈block会自动调用copy,变成堆block

1.栈 block被强指针引用时会copy到堆中

//MRC
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    int a = 2;
    MyBlock block = ^{
        NSLog(@"%d",a);
    };
    block();
    NSLog(@"%@",[block class]);
    }
    return 0;
}

在MRC环境下,block的类型是NSStackBlock,而在ARC下,block的类型是NSMallocBlock,因为有block强指针引用;

typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int  a = 1;
        NSLog(@"%@",[^{
            NSLog(@"%d",a);
        } class]);
    }
    return 0;
}

访问了局部变量,没有强指针引用,在MRC和ARC下,block都是NSStackBlock类型

2.栈 block作为函数的返回值会被copy到堆上

// arc环境,访问了局部变量,栈block
typedef void(^MyBlock)(void);
void testBlock(){
    int a = 10;
    NSLog(@"%@",[^{
        NSLog(@"%d",a);
    } class]);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      testBlock();
    }
    return 0;
}

//  arc环境,访问了局部变量,但是作为了函数的返回值,堆block
typedef void(^MyBlock)(void);
MyBlock testBlock(){
    int a = 10;
    return ^{
        NSLog(@"%d",a);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
      NSLog(@"%@",[testBlock() class]);
    }
    return 0;
}

3.cocoa API中的block和GCD中的block,会进行copy操作,在堆中


六.Block访问auto类型的oc对象

  • 如果是栈block,不会对auto变量产生强引用。
    因为栈block的内存是由系统回收的,出了作用域就会被回收,不会在作用域之外执行block,而auto变量也是出了作用域被回收,强引用oc对象的目的就是为了在执行block的时候使用的变量是正确的,栈block和auto对象的生命周期相同,所以没有必要强引用auto类型的oc对象
  • 如果block被拷贝到堆上: 1.会调用block内部的copy函数;2.copy函数会调用_Block_object_assigin函数; 3. _Block_object_assigin会根据 auto变量内存管理修饰符(__strong, __weak ,__unsafe_unretained)来做出对应的操作(强引用/弱引用)

如果block从堆上移除

  • 调用内部的dispose函数
  • dispose函数会调用内部的_Blokc_object_dispose函数.
  • _Blokc_object_dispose会根据内存管理修饰符解除对auto变量的引用

1.用strong修饰

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Book *book; // strong指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Book *_book, int flags=0) : book(_book) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->book, (void*)src->book, 3/*BLOCK_FIELD_IS_OBJECT*/);
    
}

2.用__weak修饰

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Book *__weak weakBook; // weak 指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Book *__weak _weakBook, int flags=0) : weakBook(_weakBook) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->weakBook, (void*)src->weakBook, 3/*BLOCK_FIELD_IS_OBJECT*/);
    
}
    
}

上一篇下一篇

猜你喜欢

热点阅读