技术分享-开发利器block底层实现

block的底层实现

在上一篇分享中,主要介绍了block得使用注意和一些经验心得以及开发中的一些使用场景.详情请看<技术分享-开发利器block>

那么,在block的使用注意中,你们有没有疑问说,为什么会这样呢?
说到为什么,就涉及到block的底层实现了,下面,我来介绍一下block的底层实现 - .-

block注意事项

int a = 10;
 void (^myBlock)() = ^{
  NSLog(@"a = %i", a);
   } myBlock();
    输出结果: 10
  • 等于10,结果显而易见,但是为什么呢?看完下一段的代码再作解析...
__block int a = 10;
void (^myBlock)() = ^{
  NSLog(@"a = %i", a);
  };
  a = 20;
myBlock();
输出结果: 20
  • 我在block块的外部添加了a = 20;,那么为什么可以把a的值改掉呢?

我们来看看block的底层实现:

使用clang命令:clang -rewrite-objc main.m,把刚才的main.m编译为c++文件.
编译好之后,你会发现文件夹多了一个main.cpp文件,这个就是编译出来的c++文件了,好,我们open一下.

*打开之后你会发现,哇塞,有10W+行代码...- -!
好,我们拖到最下面,编译结果如下:

#---------------编译器内会将block内部生成对应的函数---------------------------------------------------------
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内部生成对应的函数---------------------------------------------------------
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_03_d2zvdhkx4pg_j_03cp176fvw0000gp_T_main_6df904_mi_0, a);
        }

#------------------------------------------------------------------------
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; 
        int a = 10;
        void (*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        a = 50;
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    }
    return 0;
}
  • 不难发现,我们看到了void (*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));这一句,就是block块里面的,是把a的值传进去了,那么,在外面修改a的值,外面修改它的值是不知道的,a的值是不会改变的!
  • 再深一层讲,block就是&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));这个家伙的地址,这个就是一个c++结构体.
#所以,说白了,block的本质就是一个指向结构体的一个指针

那么,这个结构体是什么呢?拉上去可以看到:

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块这是一个结构体
void (*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

对应着上面的这一段结构体(就是下面这一段)

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {}
  • 传得参数都是对应的,留意a是传到_a,后面一句 a(_a)按照c++的语法,相当于把_a的值10赋值给a,那么a就是10.就是结构体里面的a就是10.

....................................................................

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

接着,留意参数(__block_impl *)myBlock)->FuncPtr对应impl.FuncPtr = fp;就是fp

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

那么,就是第一个参数的*fp,所以就相当于我们的block块的第一个参数__main_block_func_0,调了一下这个函数

void (*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

调用函数__main_block_func_0,来到下面这个代码块调用了这个函数

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_03_d2zvdhkx4pg_j_03cp176fvw0000gp_T_main_6df904_mi_0, a);
        }

把block传进去以后访问int a = __cself->a; ,那么此时a就是10,打印出来就是10

说白了就是值传递

好,我们看一下下面的就改了呢?

__block int a = 10;
void (^myBlock)() = ^{
  NSLog(@"a = %i", a);
  };
  a = 20;
myBlock();
输出结果: 20

下面是编译结果代码:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_03_d2zvdhkx4pg_j_03cp176fvw0000gp_T_main_642b4a_mi_0,(a->__forwarding->a));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        (a.__forwarding->a) = 20;

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
  • 我们发现,a传进去的是地址,那么结果显而易见了!

其实就是地址传递

总结:

  • 只有普通的定义的局部变量是值传递,其它比如是全局变量a,或者是加__block修饰的.或者是加status修饰的,都是地址传递.
  • block的本质就是一个指向结构体的指针,而编译器内会将block内部生成对应的函数,那么你就会联想到为什么block可以保存一段代码,以后再合适的位置调用了吧?!其实就是用对应的指针调用对应的函数!!!

写在最后:

好吧,明白了上面,相信对block有更进一步的认识了,有不对的地方大家可以交流一下...
						 ------make by LJW 转载请注明出处-------
原文地址:https://www.cnblogs.com/ljwiOS/p/5544313.html