一、代码——命令行模式

//main.m

#import <Foundation/Foundation.h>

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
}; struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}; struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
}; int main(int argc, const char * argv[]) {
@autoreleasepool {
// ^{
// NSLog(@"This is a block");
// }(); int age = ;
void (^block)(int, int) = ^(int a, int b){
NSLog(@"This is a block:%d", age);
NSLog(@"a:%d b:%d", a, b);
}; struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;

block(, );

    }
return ;
}

分析:以下代码的前提,因为我们知道block底层的构造就是上述结构体的构造,桥接的目的就是展示这样的结构体内部是怎样的;

struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;

二、调试

//lldb模式

1)第一个断点

2)第二个断点

3)转入汇编

4)汇编界面

分析:

1)我们发现内部变量的层次感:

第一层:包含impl、Desc、age;

第二层:impl包含isa、Flags、Reserved、FuncPtr;

2)block大括号内部的代码的第一行的地址跟FuncPtr指针指向的地址是一样的,那么block大括号内的代码是如何存放的,跟FuncPtr指针有什么关系?往下看;

三、将main.m文件转成底层实现代码(即C++代码)

1)命令行:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

说明:警告不用管;

2)找到main.cpp文件

3)拖入文件并打开

说明:不对mian.cpp文件编译的目的是,自己可以任意对该文件的代码操作而不报错;

说明:

1)上面两张图中,1、2、3是一一对应关系(1:为block要引用的外部变量;2:定义的block;3:调用block);

2)在2处,本人把一些强制转换去除了(如:void (*)等),便于阅读;

四、底层代码分析

1)block结构体(对2的分析)

很明显,block是一个指针,指向__main_block_impl_0,那__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=) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

__main_block_impl_0是个结构体,内部成员变量有__block_impl类型的结构体变量,__main_block_desc_0类型的结构体变量,外部应用变量,以及一个__main_block_impl_0方法(该方法名跟所在的结构体名称相同,为C++的一个构造方法,类似于init方法);

<1>__block_impl

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

该结构体有四个成员变量,这跟上述lldb模式下显示的成员变量相同,而第一个成员变量为isa指针,我们知道这是oc对象(实例、类、元类)的专属标志,很显然__main_block_impl_0是一个oc对象,而oc对象的本质就是在内存中为结构体,此处完全吻合;

<2>__main_block_desc_0

static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { , sizeof(struct __main_block_impl_0)};

该结构体有两个成员变量,同时定义了一个结构体变量并对其赋值,reserved赋值为0,Block_size赋值为__main_block_impl_0即block的内存大小;

<3>__main_block_impl_0构造函数

__main_block_impl_0在main 函数中传了三个参数:

//__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_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_51dde3_mi_0, age);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_51dde3_mi_1, a, b);
}

很显然,__main_block_func_0函数存放的是block大括号里面的代码,而该函数是直接赋值给__main_block_impl_0构造函数的第一个参数fp指针,而fp又赋值给__block_impl结构体中的FuncPtr变量,因此,回顾上述lldb模式,FuncPtr指针存放的地址跟37号断点处转入汇编模式显示的首地址是一样的,结合此处,可以肯定,FuncPtr变量指向block大括号内的代码,该代码存放在内存中的分配好的函数中(__main_block_func_0);

//__main_block_desc_0_DATA参数

__main_block_desc_0_DATA是一个结构体,包含了block的内存大小,最终赋值给__main_block_desc_0结构体类型变量Desc;

//age直接赋值给_age

构造函数中的第四个参数flags默认设置为0;

2)block调用(对3的分析)

block调用代码可以简化成以下代码

(__block_impl *)block->FuncPtr(block, , );

<1>参数:经上述分析,我们知道FuncPtr指向block大括号内的代码块即__main_block_func_0函数,该函数共有三个参数,block本身,和两个int类型变量,实参与形参一一对应,这点没问题;

<2>强引用:因为block是一个结构体指针,其引用结构体变量只能通过"->"形式引用(如果是结构体变量非指针,则可以通过点(.)引用——此处是C语言语法知识,稍啰嗦了点!);

但是FuncPtr并不是block(即__main_block_impl_0)结构体的成员变量,为什么能直接引用,而不应该是block->impl.FuncPtr吗?

我们看到block进行了强制转换(__block_impl *),而__block_impl结构体中是存在FuncPtr变量的,但这完全是两个不同的结构体,也不能强制转换引用啊?

我们可以看到,__block_impl结构体在__main_block_impl_0结构体中是第一个成员变量类型,即__main_block_impl_0的首地址其实就是__block_impl结构体的首地址,也就是说,__main_block_impl_0结构体的地址是从isa指针变量开始的,即__main_block_impl_0结构体在__block_impl结构体的大小范围内跟__block_impl结构体是完全重合的(其实就是同一片内存),只不过__main_block_impl_0结构体大小要比__block_impl结构体大——因此,经过强制转换后block完全可以直接引用FuncPtr成员变量;

五、结论

【1】block本质是一个oc对象,以结构体形式存放在内存中;block本身是一个指针,存放的是该结构体的内存地址(可以把block理解成函数名——函数名也是指针,指向函数的入口地址);

【2】block大括号内的代码存放在固定的函数中,该函数的入口地址存放在block结构体的成员变量指针(FuncPtr)中;

【3】block结构体分为函数调用结构体变量impl(包含isa指针变量、函数调用指针变量)、信息描述结构体指针变量Desc(包含block内存大小成员变量)、外部引用变量(age),以及构造函数;

如下图所示(构造函数没有写出来,size即为block内存大小):

GitHub

block本质探寻一之内存结构的更多相关文章

  1. block本质探寻七之内存管理

    说明: <1>阅读本问,请参照block前述文章加以理解: <2>环境:ARC: <3>变量类型:基本数据类型或者对象类型的auto局部变量: 一.三种情形 //代 ...

  2. block本质探寻二之变量捕获

    一.代码 说明:本文章须结合文章<block本质探寻一之内存结构>和<class和object_getClass方法区别>加以理解: //main.m #import < ...

  3. block本质探寻三之block类型

    一.oc代码 提示:看本文章之前,最好按顺序来看: //代码 void test1() { ; void(^block1)(void) = ^{ NSLog(@"block1----&quo ...

  4. block本质探寻八之循环引用

    说明:阅读本文,请参照之前的block文章加以理解: 一.循环引用的本质 //代码——ARC环境 void test1() { Person *per = [[Person alloc] init]; ...

  5. block本质探寻六之修改变量

    说明: <1>阅读本文章,请参照前面的block文章加以理解: <2>本文的变量指的是auto类型的局部变量(包括实例对象): <3>ARC和MRC两种模式均适用: ...

  6. block本质探寻五之atuto类型局部实例对象

    说明:阅读本文章,请参考之前的block文章加以理解: 一.栈区block分析 //代码 //ARC void test1() { { Person *per = [[Person alloc] in ...

  7. block本质探寻四之copy

    说明: <1>阅读本文,最好阅读之前的block文章加以理解: <2>本文内容:三种block类型的copy情况(MRC).是否深拷贝.错误copy: 一.MRC模式下,三种b ...

  8. block 的内存结构衍生出来的面试题

    今天在群里看到大佬们在讨论一个面试题,问如下代码在 32bit 和 64bit 系统上分别报什么错误: #import <Foundation/Foundation.h> int main ...

  9. iOS开发系列-Block本质篇

    概述 在iOS开发中Block使用比较广泛,对于使用以及一些常规的技术点这里不再赘述,主要利用C++角度分析Block内部数据底层实现,解开开发中为什么这样编写代码解决问题. Block底层结构窥探 ...

随机推荐

  1. 前端AMD、CMD和commonJs-前端知识

    前端AMD和CMD是在模块化的基础上产生并且得到大幅度的引用的. AMD 即Asynchronous Module Definition(点击链接可以查看AMD面试题),中文名是异步模块定义的意思.它 ...

  2. 热血沙城-3.2移植-古月-cocos2dx源码

    最近发现我去年学习2dx的时候移植过的一个游戏现在被放在网上出售 真是有点想笑 本人比较喜欢武侠风格的游戏,当时9秒开源了热血沙城 本着学习的态度 从2.1.2移植到3.2 用了一周的时间  中间各种 ...

  3. 在android工程中,res目录下又有anim、drawable、layout、menu、raw、values和xml文件夹,分别用来保存?

    res目录主要是存放资源文件的!layout 布局 这个就是你经常看到的与用户交互的界面的 xml 文件,就是各个 view 的排列和嵌套,没什 么好说的啦 风格和主题. 风格主要是指 view 的显 ...

  4. ciscn2018-pwn-wp

    前言 2018全国大学生网络安全竞赛 ,做了2 道题 task_supermarket change_desc 里面调用 realloc 会触发 uaf 利用 uaf 修改 obj->desc_ ...

  5. 10.Spring——框架的AOP

    1.Spring 框架的 AOP 2.Spring 中基于 AOP 的 XML架构 3.Spring 中基于 AOP 的 @AspectJ 1.Spring 框架的 AOP Spring 框架的一个关 ...

  6. [C++] 用Xcode来写C++程序[1] 新建C++项目工程

    用Xcode来写C++程序[1] 新建C++项目工程 第一节从新建工程并编译C++源码开始 新建工程 源码: // // main.cpp // YeHelloWorld // // Created ...

  7. 绕过CDN查找网站真实IP方法收集

    方法1很简单,使用各种多地 ping 的服务,查看对应 IP 地址是否唯一,如果不唯一多半是使用了CDN, 多地 Ping 网站有: http://ping.chinaz.com/ http://pi ...

  8. 【4】python函数基础

    ---恢复内容开始--- 案例1:时间下一秒程序 #__author:"吉勇佳" #date: 2018/10/14 0014 #function: timestr=input(& ...

  9. XtraEditors三、LookUpEdit、GridLookUpEdit、SearchLookUpEdit

    https://documentation.devexpress.com/WindowsForms/DevExpress.XtraEditors.LookUpEditBase.class 一.Look ...

  10. 我的Java之旅——第一个Java程序

    在简单的看了一点Java的基本内容后,我开始尝试写自己的第一个Java程序.由于某些原因,学校官方的教务APP看不了自己这学期的平均绩点,就想着自己动手,写一小段代码,算一下自己的平均绩点.程序的功能 ...