美文网首页
关于block(一)----clang源码与变量的引用

关于block(一)----clang源码与变量的引用

作者: stonly916 | 来源:发表于2018-04-27 18:09 被阅读0次

block 其实就是一个结构体+函数指针,其赋值就是传递block对象(结构体);

下面代码我们将其转为c的源码,clang我用的是这段命令:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk XXXX.m

代码一:
void(^myBlock)(void) = ^() {
    printf("ss");
};
myBlock();
clang源码:
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __ClangProxy__staff_block_impl_0 {
  struct __block_impl impl;
  struct __ClangProxy__staff_block_desc_0* Desc;
  __ClangProxy__staff_block_impl_0(void *fp, struct __ClangProxy__staff_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ClangProxy__staff_block_func_0(struct __ClangProxy__staff_block_impl_0 *__cself) {
        printf("ss");
    }

static struct __ClangProxy__staff_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ClangProxy__staff_block_desc_0_DATA = { 0, sizeof(struct __ClangProxy__staff_block_impl_0)};

调用部分源码:

void(*myBlock)(void) = ((void (*)())&__ClangProxy__staff_block_impl_0((void *)__ClangProxy__staff_block_func_0, &__ClangProxy__staff_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

观察上面的源码,能发现struct __block_impl结构体,

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

这个结构体包含有isa指针,根据我们对OC对象的理解,这个结构体类似于元类对象,
其中flags标志暂时没看出有什么用,只知道在构造函数里初始化为0,reserved则是保留参数,
void *FuncPtr这个就是block的实现内容了,这个函数指针指向了对应block的实现函数;
当然不是完整部分,一个完整的block不仅应该包含实现函数,同时还要有其引用的外部变量,还有对自身的描述。

再来看看struct __ClangProxy__staff_block_impl_0

struct __ClangProxy__staff_block_impl_0 {
  struct __block_impl impl;
  struct __ClangProxy__staff_block_desc_0* Desc;
  __ClangProxy__staff_block_impl_0(void *fp, struct __ClangProxy__staff_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

这个结构体的命名也比较有趣,是类名+函数/方法名+block_impl+数字(第几个block),这个结构体中有两个成员变量,一个构造函数(这里应该是C++用法),我们先看第一个成员变量struct __block_impl impl,这个就是block的实现部分,其中包含函数指针上面提到过了;其次我们再看第二个成员struct __ClangProxy__staff_block_desc_0* Desc:

static struct __ClangProxy__staff_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ClangProxy__staff_block_desc_0_DATA = { 0, sizeof(struct __ClangProxy__staff_block_impl_0)};

其中就一个Block_size,方便block初始化,这里已经初始化了一个值:__ClangProxy__staff_block_desc_0_DATA,取的是struct __ClangProxy__staff_block_impl_0的size。
再看构造函数,其实这个比较简单就是传入参数(void *fp, struct __ClangProxy__staff_block_desc_0 *desc, int flags=0),第一个是block实现的函数指针,第二个就是block描述,第三个在这里固定0(实际运行可能不是0,暂不讨论),
到这里基本就看完了block的结构了,下面看

block调用部分1:
void(*myBlock)(void) = (
   (void (*)())  &__ClangProxy__staff_block_impl_0(
      (void *)__ClangProxy__staff_block_func_0,
      &__ClangProxy__staff_block_desc_0_DATA
   )
);

上面代码我简单的做了下换行,方便观察;

  • 首先就是
    void(*myBlock)(void) = (void(*)()) &__ClangProxy__staff_block_impl_0(...)
    这个比较好理解,就是将右边的__ClangProxy__staff_block_impl_0构造函数创建的结构体通过void(*)()获取指针,将指针赋值给myBlock。

  • 后面就是传参了,分别是block的实现函数指针__ClangProxy__staff_block_func_0还有block描述

block调用部分2:
/*第一行*/((void (*)(__block_impl *))
/*第二行*/((__block_impl *)myBlock)->FuncPtr)
/*第三行*/((__block_impl *)myBlock);

这里从第二行myBlock开始看,(__block_impl *)myBlock将其显示转换为结构体struct __block_impl *类型,这里需要解释下:

myBlock明明是struct __ClangProxy__staff_block_impl_0 *为什么可以直接转换为struct __block_impl *
原因很简单,因为struct __block_impl是结构体struct __ClangProxy__staff_block_impl_0的第一个成员变量,结构体的存储分配是按成员变量顺序来的,所以从指针开始的那段偏移量(sizeof(struct __block_impl))就是第一个成员变量,是可以强制转换的。

然后用->FuncPtr获取到block的实现函数指针,通过第一行(void (*)(__block_impl *))转换为带参数类型为struct __block_impl*的函数指针;再看第三行就是传入参数myBlock;至于为什么不是跟下面函数一样传入struct __ClangProxy__staff_block_impl_0*参数,或许是因为转化方便,毕竟他们的指针指向是一样的

block的实现函数
static void __ClangProxy__staff_block_func_0(struct __ClangProxy__staff_block_impl_0 *__cself) {
        printf("ss");
    }

参数是struct __ClangProxy__staff_block_impl_0 *,没什么特别的。

代码二:

如果上面都理解的差不多了,我们再看下面这个block(ARC,block赋值会copy到堆):

    __block int i = 2;
//*    printf("i在block外的地址%p\n",&i);
//+    __weak typeof(self) weakSelf = self;
//    __NSMallocBlock__
    void (^staBlock)(void) = ^{
//+        __strong typeof(self) strongSelf = weakSelf;
        self.num = 10;
//*        printf("i在block内的地址%p\n",&i);
        i = 1;
//*        printf("i在block内赋值后的地址%p\n",&i);
        printf("112233");
    };
//+    self.bbllkk = staBlock;
    staBlock();
/*
i在block外的地址0x7ffeedeb0b18
i在block内的地址0x604000226ff8
i在block内赋值后的地址0x604000226ff8
*/
clang源码:
struct __Block_byref_i_2 {
  void *__isa;
__Block_byref_i_2 *__forwarding;
 int __flags;
 int __size;
 int i;
};
struct __ClangProxy__staff_block_impl_10 {
  struct __block_impl impl;
  struct __ClangProxy__staff_block_desc_10* Desc;
  ClangProxy *self;
  __Block_byref_i_2 *i; // by ref
  __ClangProxy__staff_block_impl_10(void *fp, struct __ClangProxy__staff_block_desc_10 *desc, ClangProxy *_self, __Block_byref_i_2 *_i, int flags=0) : self(_self), i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ClangProxy__staff_block_func_10(struct __ClangProxy__staff_block_impl_10 *__cself) {
  __Block_byref_i_2 *i = __cself->i; // bound by ref
  ClangProxy *self = __cself->self; // bound by copy


        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setNum:"), 10);
        (i->__forwarding->i) = 1;
        printf("112233");
    }
static void __ClangProxy__staff_block_copy_10(struct __ClangProxy__staff_block_impl_10*dst, struct __ClangProxy__staff_block_impl_10*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __ClangProxy__staff_block_dispose_10(struct __ClangProxy__staff_block_impl_10*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __ClangProxy__staff_block_desc_10 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ClangProxy__staff_block_impl_10*, struct __ClangProxy__staff_block_impl_10*);
  void (*dispose)(struct __ClangProxy__staff_block_impl_10*);
} __ClangProxy__staff_block_desc_10_DATA = { 0, sizeof(struct __ClangProxy__staff_block_impl_10), __ClangProxy__staff_block_copy_10, __ClangProxy__staff_block_dispose_10};
clang调用代码
    __attribute__((__blocks__(byref))) __Block_byref_i_2 i = {(void*)0,(__Block_byref_i_2 *)&i, 0, sizeof(__Block_byref_i_2), 2};

    void (*staBlock)(void) = ((void (*)())&__ClangProxy__staff_block_impl_10((void *)__ClangProxy__staff_block_func_10, &__ClangProxy__staff_block_desc_10_DATA, self, (__Block_byref_i_2 *)&i, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)staBlock)->FuncPtr)((__block_impl *)staBlock);

这个block的总体结构和上面差不多,但是多了一些新的内容,

1.首先这个block内有这样两句代码
self.num = 10;
i = 1;

这里也就引用了外部变量i和self,那么block到底如何引用外部变量的呢,我们从block结构体中可以看到:

struct __ClangProxy__staff_block_impl_10 {
  struct __block_impl impl;
  struct __ClangProxy__staff_block_desc_10* Desc;
  ClangProxy *self;
  __Block_byref_i_2 *i; // by ref
  __ClangProxy__staff_block_impl_10(void *fp, struct __ClangProxy__staff_block_desc_10 *desc, ClangProxy *_self, __Block_byref_i_2 *_i, int flags=0) : self(_self), i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

这里有两个成员一个self,还有一个就是 i

  • 其中 self 很好理解ClangProxy *self,就是直接引用的类的实例,这里也是可能出现循环引用的关键点,就是block强引用了self
  • 对于 i 你会发现,在block外我们创建的是__block int i = 2,而这里却是__Block_byref_i_2类型,原因是我们引用了__block,这里就会将原本的int类型转换为结构体struct __Block_byref_i_2
struct __Block_byref_i_2 {
  void *__isa;
__Block_byref_i_2 *__forwarding;
 int __flags;
 int __size;
 int i;
};

这个结构体第一个成员就是__isa,类似于OC对象;关键是第二个成员__Block_byref_i_2 *__forwarding,我们发现在block的实现函数void __ClangProxy__staff_block_func_10(...){...}中是这么调用外部变量 i 的:(i->__forwarding->i) = 1;,这句代码第一个i是结构体struct __Block_byref_i_2,第二个i就是这个结构体第五个成员int i;了,为什么要这样写呢,我们先看下面这段代码:

    __block int h = 9;
    printf("h在block外的地址%p\n",&h);
    //__NSStackBlock__,直接在栈中被调用
    ^{
        printf("h在block内的地址%p\n",&h);
        h = 11;
        printf("h在block内赋值后的地址%p\n",&h);
    }();
/*
h在block外的地址0x7ffeec265c70
h在block内的地址0x7ffeec265c70
h在block内赋值后的地址0x7ffeec265c70
*/

首先我们要知道,这个block没有copy也没有赋值属于栈blockNSStackBlock__block int h在clang源码中会转换为结构体struct __Block_byref_h_0,因为一直都在栈内所以结构体struct __Block_byref_h_0没有被拷贝,自然&h指针,其clang代码&(h.__forwarding->h),__forwarding是指向自己的,最后还是指向栈中结构体h中的成员变量int h,所以三次打印的h地址都是一样的0x7ffeec265c70。如下图(网上找的)

96DE3DF0-488C-4D5B-8FE4-ACA0D58F3D47.png
我们再回头看代码二,为什么这里输出i的地址在block内外不一样呢?
/*
i在block外的地址0x7ffeedeb0b18
i在block内的地址0x604000226ff8
i在block内赋值后的地址0x604000226ff8
*/

这是因为这里block有赋值操作,原本栈中的block被copy到堆中,这个staBlock是存储在堆中的__NSMallocBlock__。i 在block外的时候即栈中struct __Block_byref_i_2中的__forwarding是指向自身的,但是在block被拷贝到堆中的时候,堆中重新复制了一份变量__Block_byref_i_2 *i,栈中i的__forwarding就指向了堆中的i,而堆中的i的__forwarding则指向了自己,如下(网上找的):

sdf.jpg
由于我们输出的是结构体中的int i这个基本类型的地址,在结构体copy到堆中的时候,即copy操作发生后,会在堆上创建新的结构体,新的结构体中的i的地址自然和copy操作前地址不一样;
2.经过对比,我们发现这里还多了这样两个函数
static void __ClangProxy__staff_block_copy_10(struct __ClangProxy__staff_block_impl_10*dst, struct __ClangProxy__staff_block_impl_10*src) {
  _Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
  _Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __ClangProxy__staff_block_dispose_10(struct __ClangProxy__staff_block_impl_10*src) {
  _Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
  _Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}

这两个函数并不是所有block都有的,当block中引用了结构体变量或者OC变量时(OC变量其实也可以理解为一种结构体),才会有这两个函数,这两个函数也只会处理block引用的结构体,其他基本类型等都不会在这两个函数里。

第一个函数__ClangProxy__staff_block_copy_10(...)有两个__ClangProxy__staff_block_impl_10 *结构体指针参数,分别是dst(destination,目的地),src(source,源),src就是在栈中的block结构体,而dst就是堆中的block结构体,这里有两个变量引用,所以有两个变量操作。

  • 第一个是self,这是个OC对象,取栈中的self指针src->self,再获取堆中结构体的成员self指针的存储地址(其实就是获取成员变量在结构体中首地址)&dst->self,3标识表示是object(8表示地址引用),正因为是object,所以只要引用指针,引用计数器+1就可以了;
  • 第二个是 i,它并不是一个基本类型,而是结构体指针,block外 i 是这样声明的__block int i = 2;,上面已有说明,这里系统会将其转换为一个结构体struct __Block_byref_i_2
    OC对象其实也是结构体指针,但是OC对象与这里的结构体i又是不一样的:OC对象在ARC下由系统管理自动释放池,当引用计数为0,则释放。
    这里的结构体指针 i 不是OC对象,所以这里在copy到堆中的时候就不是引用指针了,从&dst->i看出,这是一个存储空间的首地址(堆中block结构体的成员 i 的存储地址),我们自然可以联想到这里应该是深拷贝,重新malloc了一个空间,然后将src->i的内容拷过去了,自然,结构体i 中 成员i 的地址就会发生变化,这里也就和上面那张引用变量在栈、堆中的关系图联系到一起了

第二个函数__ClangProxy__staff_block_dispose_10(...),这里只有源栈中block结构体的引用,这是因为在堆block释放的时候,由于堆block中 结构体i 是深拷贝过来的新对象,对堆block释放的时候需要对源block中引用的变量进行主动释放

相关文章

  • 关于block(一)----clang源码与变量的引用

    block 其实就是一个结构体+函数指针,其赋值就是传递block对象(结构体); 下面代码我们将其转为c的源码,...

  • Block源码解析和深入理解

    Block源码解析和深入理解 Block的本质 Block是"带有自动变量值的匿名函数". 我们通过Clang(L...

  • 笔记 - Block

    目录 序 block实质 源码分析 自动变量值的截获 循环引用问题 资料推荐 关于block,我们在开发中经常用到...

  • Block嵌套导致的循环引用

    self -> obj -> block这种情况会导致self与block的循环引用 根据clang命令生成的c+...

  • iOS block详细知识点

    Block与外界变量 1、截获自动变量(局部变量)值 (1)默认情况 对于 block 外的变量引用,block ...

  • block分析(下)

    block通过clang分析 带着下面的疑问,我们去探索block原理 探索block底层源码 block在底层是...

  • iOS - Block

    源码解析 clang -rewrite-objc file.m 一、Block 是什么 Block 是将函数及其执...

  • Block __block修饰符

    如果block需要捕获某个变量后进行赋值,这个变量在声明时需要使用 __block修饰。 clang -rewri...

  • 十四、Block之(六)Block的__block的内存管理

    一、强指针引用的block,内部引用OC对象的局部变量。 强指针引用的block,内部引用OC对象的局部变量,需要...

  • Block的内部工作原理

    __block修饰block外部变量 转成c++后代码(clang -rewrite-objc main.m)如下...

网友评论

      本文标题:关于block(一)----clang源码与变量的引用

      本文链接:https://www.haomeiwen.com/subject/ricblftx.html