美文网首页
Block底层分析

Block底层分析

作者: 6ffd6634d577 | 来源:发表于2019-08-01 20:34 被阅读0次

Block内存关系
Block经典问题循环引用&解决
Block底层分析
Block底层HooK

1. 研究工具:clang

为了研究编译器是如何实现 block 的,我们需要使用 clang。clang 提供一个命令,可以将 Objetive-C 的源码改写成 c 语言的,借此可以研究 block 具体的源码实现方式

首先cd到代码文件目录,然后执行clang命令

clang -rewrite-objc main.m
 int main(int argc, char * argv[]) {
    @autoreleasepool {
         typedef void (^blk_t)(void);
         blk_t block = ^{ printf("Hello, World!\n");
    };
         block();
         // return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }
}

如果我们引入了UIKit框架,那么此命令会报错#import <UIKit/UIKit.h>报错

main.m:10:9: fatal error: 'UIKit/UIKit.h' file not found

好事多磨啊,这是因为我们没有指定链接的sdk


clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.1.sdk main.m


image.png

2 实现分析
这里只选取部分关键代码。

不难看出int main(int argc, char * argv[]) 就是主函数的实现。

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        typedef void (*blk_t)(void);
        blk_t block = ((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);

    }
}

其中,__main_block_impl_0是block的一个C++的实现(最后面的_0代表是main中的第几个block),也就是说也是一个结构体。

(1) __main_block_impl_0
__main_block_impl_0定义如下:

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

这里impl.isa 的类型为_NSConcreteStackBlock,由于 clang 改写的具体实现方式和 LLVM 不太一样,所以这里 isa 指向的还是_NSConcreteStackBlock。但在 LLVM 的实现中,开启 ARC 时,block 应该是 _NSConcreteGlobalBlock 类型。

(2) __block_impl
其中__block_impl的定义如下:

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

其结构体成员如下:

  • isa,指向所属类的指针,也就是block的类型
  • flags,标志变量,在实现block的内部操作时会用到
  • Reserved,保留变量
  • FuncPtr,block执行时调用的函数指针

可以看出,它包含了isa指针(包含isa指针的皆为对象),也就是说block也是一个对象(runtime里面,对象和类都是用结构体表示)。

(3) __main_block_desc_0
是block的描述信息 的结构体,__main_block_desc_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)};

其结构成员含义如下:

  • reserved:保留字段
  • Block_size:block大小(sizeof(struct __main_block_impl_0))

以上代码在定义__main_block_desc_0结构体时,同时创建了__main_block_desc_0_DATA,并给它赋值,以供在main函数中对__main_block_impl_0进行初始化。

(4) __main_block_func_0
如上的main函数中,__main_block_func_0也是block的一个C++的实现,是block要执行的函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 printf("Hello, World!\n");
        }

(5) 综合可知:

  • __main_block_impl_0的isa指针指向了_NSConcreteStackBlock。
  • 从main函数的main.cpp中看,__main_block_impl_0的FuncPtr指向了函数__main_block_func_0。
  • __main_block_impl_0的Desc也指向了定义__main_block_desc_0时就创建的__main_block_desc_0_DATA,其中纪录了block结构体大小等信息。

上面block代码,没有获取任何外部变量,应该是 _NSConcreteGlobalBlock类型的。该类型的block和函数一样 存放在 代码段 内存段。内存管理简单。

2.1 使用自动变量的Block分析

int main(){
    int age = 100;
    const char *name = "zyt";
    void (^blk) (void) = ^{
        printf("block invoke age = %d age = %s", age, name);
    };
    age = 101;
    blk();
    return 0;
}

使用clang -rewrite-objc main.m命令重写为C++语法的源文件。对比上一个的数据结构方法和调用步骤大体上是一样的,只针对变化的地方进行分析

__main_block_impl_0结构体的变化
增加了两个成员:int类型的age成员、char*类型的name成员,用于保存外部变量的值,在初始化的时候就会使用自动变量的值初始化这两个成员的值

调用的变化

__main_block_func_0函数的参数struct __main_block_impl_0 *__cself在这个例子有使用到了,因为两个自动变量对应的值被保存在__main_block_impl_0结构体中了,方法中有使用到这两个变量,直接从__main_block_impl_0结构体中获取这两个值,但是这两个值是独立于自动变量的存在了

由上面的分析,可以得出如下的结论:使用变量的Block调用本质上是使用函数指针调用函数,参数是保存在block的结构体中的,并且保存的是值而不是引用

2.2 使用__block修饰的变量的Block分析

Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值

// 源代码
int main(int argc, const char * argv[]) {
    __block int age = 100;
    const char *name = "zyt";
    void (^blk) (void) = ^{
        age += 2;
        printf("block invoke age = %d age = %s", age, name);
    };
    age += 1;
    blk();
}

使用clang -rewrite-objc main.m命令重写为C++语法的源文件,对结果分析如下的注释,该转换后的代码做了些许的调整,包括代码的缩进和添加了结构体声明,使得该代码可以直接运行

// clang改写后的代码如下,以下代码是经过调整可以直接运行的
struct __main_block_desc_0;


// __block_impl结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

// __block修饰的变量对应的结构体,里面包含了该变量的原始值也就是age成员,另外还有一个奇怪的__forwarding成员,稍后我们会分析它的用处

struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};

// __main_block_impl_0结构体包含了四个成员__block_impl类型的impl成员、__main_block_desc_0类型的Desc成员、__Block_byref_age_0 *类型的age成员、char类型的name成员;一个构造方法__main_block_impl_0,构造方法中初始化impl成员、Desc成员、age成员和name成员,比起上一个结构体的变化是age的类型变为了包装自动变量的结构体了
struct __main_block_impl_0 {
__block_impl impl;
__main_block_desc_0 Desc;
const char *name;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp,
struct __main_block_desc_0 *desc,
const char *_name,
__Block_byref_age_0 *_age,
int flags=0) :
name(_name),
age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

// Block执行的方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
const char *name = __cself->name; // bound by copy


(age-&gt;__forwarding-&gt;age) += 2;
printf("block invoke age = %d age = %s", (age-&gt;__forwarding-&gt;age), name);

}

// Block拷贝函数
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/BLOCK_FIELD_IS_BYREF/);
}


// Block销毁函数
static void __main_block_dispose_0(struct __main_block_impl_0src) {
_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};


// 重写后的入口函数main
int main(int argc, const char * argv[]) {
attribute((blocks(byref))) __Block_byref_age_0 age = {
(void*)0,
(__Block_byref_age_0 *)&age,
0,
sizeof(__Block_byref_age_0),
100};
const char *name = "zyt";
struct __main_block_impl_0 blockImpl =
__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA,
name,
(__Block_byref_age_0 )&age,
570425344);
void (blk) (void) = ((void ()())&blockImpl);
(age.__forwarding->age) += 1;
((void ()(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}

__block修饰自动变量转换为C++代码的结构体关系图:


image.png

该重写的代码大部分和上面分析过的例子代码是类似,发现增加了两个处理方法Block拷贝函数__main_block_copy_0和Block销毁函数__main_block_dispose_0,这两个函数会保存在__main_block_desc_0结构体的copy和dispose成员中;另外添加了一个__Block_byref_age_0结构体类型用户处理__block修饰的自动变量。以下针对这两点从源代码的角度进行一个分析

__main_block_copy_0方法中调用到的_Block_object_assign可以在 runtime.c 这里找到 ,主要看下_Block_object_assign方法里面的处理逻辑

  • flags参数值为8,是BLOCK_FIELD_IS_BYREF枚举对应的值,会走到_Block_byref_assign_copy方法的调用步骤
  • _Block_byref_assign_copy方法会在在堆上创建Block_byref对象,也就是Block对象,并且把栈上和堆上的Block对象的forwarding属性值都修改为指向堆上的Block对象,这样使用两个对象的修改值都会修改为同一个地方

栈上的__block自动变量__forwarding指向关系以及拷贝到堆上之后__forwarding指向关系如下图所示

image.png

相关文章

  • Block探索

    Block内存关系Block经典问题循环引用&解决Block底层分析Block底层HooK 程序占用内存分类 栈区...

  • Block底层分析

    Block内存关系Block经典问题循环引用&解决Block底层分析Block底层HooK 1. 研究工具:cla...

  • block分析(下)

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

  • Block底层hook

    Block内存关系Block经典问题循环引用&解决Block底层分析Block底层HooK 前言 如何反编译出微信...

  • Block经典问题循环引用&解决

    Block内存关系Block经典问题循环引用&解决Block底层分析Block底层HooK 1.循环引用怎么产生的...

  • OC底层面试知识点之 —— Block底层原理!

    本文将介绍block的类型,循环引用的解决方法以及block底层分析 Block简介 Block定义:带有自动变量...

  • Block 深入浅出

    标签:block block的类型 循环引用的解决方法 block底层的分析 本章节主要介绍 1.block的类型...

  • Block实现原理

    主要介绍block的类型和底层分析 block类型 block主要由三种类型 NSGlobalBlock:全局bl...

  • block分析

    本文主要介绍block的类型、循环引用的解决方法以及block底层的分析 block 类型 block主要有三种类...

  • iOS-底层原理:Block

    这里主要介绍block的类型、循环引用的解决方法以及block底层的分析 block 类型 block主要有三种类...

网友评论

      本文标题:Block底层分析

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