Block探究

2018-12-16  本文已影响0人  飞奔的小鲨鱼

在我们实际的开发过程中,block的使用可以说是经常遇到到的了吧,GCD,网络请求,动画都随处可见block的影子,能熟练的使用block是不是就意味着你真正的了解block呢?正所谓知其然,知其所以然。

void (^block)() = ^{
    NSLog(@"无参数无返回值的block");
};
block();

2.有参无返回值的block

void (^block)(int) = ^(int a){
      NSLog(@"有参数无返回值的block  %d",a);
 };
 block(5);

3.有参有返回值的block

int (^block)(int) = ^(int a){
     NSLog(@"有参数有返回值的block  %d",a);
     return a + 5;
 };
 block(5);
typedef void (^sumBlock)(int,int);

@property (nonatomic, copy) sumBlock sumBlc;

   self.sumBlc = ^(int a,int b){
        NSLog(@"sum a + b  = %d",a+b);
    };
    
    self.sumBlc(3,5);
- (void)blockTest{
    NSString *(^stringBlc)(int) = ^(int a){
        // 需要定义的操作
        return [NSString stringWithFormat:@"%dabc",a];
    };
    NSString * str1 = stringBlc(123);
    NSString * str2 = stringBlc(345);
    NSString * str3 = stringBlc(456);
}

简单的介绍了一下block的使用,接下来我们就看看它的底层实现到底是什么样子的,首先创建一个Mac os程序,在main.m函数中写一个简单block

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

然后进入相应的文件夹,在终端输入clang -rewrite-objc main.m,发现多了一个main.cpp的文件,这是一个9w多行的代码,在文件的最下面我们发现了main函数

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int age = 10;
        void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
                                                 (void*)__main_block_func_0, 
                                                 &__main_block_desc_0_DATA,
                                                 age)
       //如果block内部没有用到age,只有前两个参数
       //__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DAT)
        );

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

我们发现__main_block_impl_0这个函数传入了3个参数__main_block_func_0, &__main_block_desc_0_DATA, age,并且将函数的地址赋给了block,那我们看一下__main_block_impl_0

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;
  }
};

__main_block_impl_0是一个结构体,它里面有两个结构体:impl和 Desc,age 和一个构造函数,并且将传入的__main_block_func_0赋值给了impl.FuncPtr,将传入的&__main_block_desc_0_DATA赋值给了Desc,在构造函数中为age完成了赋值,这一狗仔函数主要是为这两个结构体赋值,那么我们接下来看看这两个结构体。

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

impl是一个__block_impl类型的结构体,里面有一个isa指针,这个isa的赋值是block的地址,所以说block实质上是一个OC对象,Flags=0,FuncPtr是传入的__main_block_func_0

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)};

Desc是一个__main_block_desc_0类型的结构体,里面只有reserved和Block_size,而__main_block_desc_0_DATA的作用就是保存了这个block的大小。

最后就只剩下__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_0d2748_mi_0,a,b);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_0d2748_mi_1,age);
        }

这个函数首先传入了a,b两个参数,从__cself中取出block代码块要用的age,然后是两个NSLog,那我们可以看出这个__main_block_func_0的作用就是保存了block的代码块实现。

至此,__main_block_impl_0中出现的函数和结构体我们都已经看过了,我们可以大致分析如下:

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

它们之间的关系可以用下图表示:


block的结构图.png

那我们看看block在这么调用的

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
                                                (__block_impl *)block, 5, 5);

由于block指向的是__main_block_impl_0的地址,而impl是__main_block_impl_0结构体中的第一个成员,里面保存着block的代码块,将block转化为(__block_impl *)类型去调用block的代码块。

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

我们知道如果age没有用__block修饰的话,在block中直接修改age的值编译器直接会报错,加上就可以直接修改age的值,那__block到底做了些什么?
将main.m文件clang之后我们发现以下不同:

__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
                                    (void*)0,
                                    (__Block_byref_age_0 *)&age, 
                                     0, 
                                     sizeof(__Block_byref_age_0), 
                                     10
                              };
void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
                                    (void *)__main_block_func_0, 
                                    &__main_block_desc_0_DATA, 
                                    (__Block_byref_age_0 *)&age, 
                                    570425344)
                              );

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
                                    (__block_impl *)block, 5, 5);
struct __Block_byref_age_0 {
  void *__isa;          // 0
__Block_byref_age_0 *__forwarding; // 指向自己的指针
 int __flags;           // 0
 int __size;            // __Block_byref_age_0的大小
 int age;               // age
};

首先定义了一个__Block_byref_age_0类型的结构体age,__forwarding指向结构体age自己,里面封装了外部变量age。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, 
                      struct __main_block_desc_0 *desc, 
                      __Block_byref_age_0 *_age, 
                      int flags=0) 
                      : age(_age->__forwarding) 
{
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_impl_0内部有一个__Block_byref_age_0类型的age,在构造函数中_age->__forwarding指向了结构体自己。

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref

       NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_0,a,b);
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_1,
                  (age->__forwarding->age));
       (age->__forwarding->age) = 20;
       NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_2,
                  (age->__forwarding->age));
        }

首先取出__main_block_impl_0中的结构体age,在修改age的值得时候,先通过__forwarding找到结构体age的地址,再去修改age中真正的age对象的值。

static void __main_block_copy_0(struct __main_block_impl_0*dst, 
                                struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} 
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), 
                                              __main_block_copy_0, 
                                              __main_block_dispose_0};

因为在C语言的结构体中,编译器没法很好的进行初始化和销毁操作。这样对内存管理来说是很不方便的。所以就在 __main_block_desc_0结构体中间增加成员变量 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*)void (*dispose)(struct __main_block_impl_0*),利用OC的Runtime进行内存管理。
这里的_Block_object_assign_Block_object_dispose就对应着retain和release方法。

_Block_object_assign函数调用时机及作用

当block进行copy操作的时候就会自动调用__main_block_desc_0内部的__main_block_copy_0函数,__main_block_copy_0函数内部会调用_Block_object_assign函数。
_Block_object_assign函数会自动根据__main_block_impl_0结构体内部的age是什么类型的指针,对age对象产生强引用或者弱引用。

_Block_object_dispose函数调用时机及作用

当block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数。
_Block_object_dispose会对age对象做释放操作,类似于release,也就是断开对age对象的引用,而age究竟是否被释放还是取决于age对象自己的引用计数。

iOS底层原理总结 - 探寻block的本质
深入研究Block捕获外部变量和__block实现原理

上一篇下一篇

猜你喜欢

热点阅读