block 底层原理

111 阅读6分钟

block底层原理(出道文章)

以后就选择掘金作为文章发表平台了。希望自己多多学习,多多产出。

什么是block

  • block本质是一个OC对象,因为它内部有一个isa指针
  • block封装了函数调用以及函数调用环境的OC对象。因为block会捕获执行代码以及变量

block底层代码

OC代码如下

int main(int argc, const char * argv[]) {
   void (^block)(int) = ^(int a){
            a = 12;
            printf("%d\n", a);
        };
  return 0;
}

使用pxcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件 将OC文件转译为C++文件,代码如下

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, int a) {
​
            a = 12;
        }
​
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;    //block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
​
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        /**
        __main_block_impl_0是一个函数指针,即第22849行的  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)。
         参数1:函数指针:(void *)__main_block_func_0:第22856行的函数,内部封装的在block内实际写的代码
         参数2:   结构体指针:__main_block_desc_0, 第22861行。
         */
        void (*block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        int b = 2;
        ((void (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, b);
    }
    return 0;
}

block底层其实是一个结构体,结构体名字叫 __函数名_block_impl_序号。解释一下,我们是在main函数定义的一个block,所以这个block的底层结构体叫 __main_blcok_impl_0,如果是在main函数里定义的第二个block,其底层结构体名字就叫 __mian_block_impl_1。如果在 test 函数里面定义了一个block,其底层结构体名字会是什么? ·__test_block_impl_0

__mian_block_impl_0 结构体

struct __main_block_impl_0 {
  struct __block_impl impl;   //结构体
  struct __main_block_desc_0* Desc; //结构体指针,指向一个结构体,内部两个成员变量, flags不用管,block_size表示当前block的大小
  __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;
  }
};

主要有三个成员:

struct __block_impl impl;

指向 __block_impl结构体指针。

struct __block_impl {
  void *isa;    //指向block类对象的isa指针
  int Flags;    //默认在初始化时会被设置为0
  int Reserved; //
  void *FuncPtr;  //函数地址,该函数地址的函数内代码就是block里面写的代码
};

struct __main_block_desc_0* Desc

结构体指针:

static struct __main_block_desc_0 {
  size_t reserved;      //默认为0,不知道是什么
  size_t Block_size;    //block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};  //初始化函数,直接赋予其block结构体的大小

结构体初始化函数 __main_block_impl_0(**void** *fp, **struct** __main_block_desc_0 *desc, **int** flags=0)

 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;  //指向block的类型
    impl.Flags = flags;                 //flag默认为0
    impl.FuncPtr = fp;                  //封装block代码函数的函数指针
    Desc = desc;                        //desc结构体
  }
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
​
            a = 12;
        }

block的变量捕获

block变量捕获机制

  • 局部变量:捕获值
  • 静态变量:捕获指针
  • 全局变量:不捕获,直接访问

block对变量捕获 auto变量 对底层结构体的影响

__main_block_impl_0结构体会增加捕获的变量

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  int a;  //注意这里
  
  __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;
  }
};

block变量捕获 对象变量 对底层结构体的影响

1.__main_block_impl_0结构体会增加捕获的 变量的指针

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  NSObject *obj;		//注意这里
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

2.在 __main_block_desc_0结构体中增加 copy 方法和 dispose 方法。这两个方法会在后面的 block捕获对象内存管理 中说到

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

block变量捕获 __block修饰的变量 对底层结构体的影响

1.不论 __block 修饰的是 auto变量 还是 对象变量。都会把 变量 封装成一个 __Block_byref 对象,并添加到 __main_block_impl_0中。

  • __Block_byref 结构体详情
struct __Block_byref_c_0 {
  void *__isa;							//有isa,说明这是一个OC对象
__Block_byref_c_0 *__forwarding;	//forwarding指针在栈上是指向自己,当拷贝到堆上时,栈上的block的forwarding指针指向堆
 int __flags;							//标志位
 int __size;							//结构体大小
 int c;										//实际的变量
};

调用时传入的东西

 __attribute__((__blocks__(byref))) __Block_byref_c_0 c = {(void*)0,(__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 10};
  • __main_block_impl_0结构体变化
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_c_0 *c; // by ref	注意这里
  __Block_byref_d_1 *d; // by ref	注意这里
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_c_0 *_c, __Block_byref_d_1 *_d, int flags=0) : c(_c->__forwarding), d(_d->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

2.在 __main_block_desc_0结构体中增加 copy 方法和 dispose 方法

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

总结

1.block在捕获 auto变量 时仅仅会在 __main_block_impl_0 中添加一个该 auto变量对象

2.block在捕获 对象变量 的时候会在 __main_block_impl_0 中添加改对象指针,并且在 __main_block_desc_0 中添加 copydispose 方法。

3.__block修饰符会将 变量 (不管是 auto 还是 对象变量) 都会将变量变成 __block_byref 对象。同时在 __main_block_desc_0 结构体中添加 copy dispose 方法

__block对象内存管理

  • block在捕获对象变量时会根据对象的修饰符(__strong__weak_unsafe_retain)来对该对象进行引用计数管理。如果是strong就引用计数+1。

  • block捕获变量之后默认是在栈上的,在MRC环境下执行copy方法,或者ARC环境下编译器自动将其拷贝到堆上。

    • 会在 __main_block_desc_0 结构体中添加 copydispose 方法。
    • 在栈上的时候会增加引用计数,copy的时候会调用 copy 方法将block拷贝到堆上,引用计数会再次+1
    • 如果是被__block修饰过的 __block_byref 对象,block在栈上的时候 __block_byref__forwarding 指针是指向自己,但是在拷贝到堆上的时候,会使用 copy 函数,将block拷贝到堆上,同时将 栈上block的 __forwarding 指针指向堆上的block。
    • 堆上的block在销毁的时候会调用 dispose方法销毁。