iOS Block的本质(一)
iOS Block的本质(一)
1.对block有一个基本的认识
- block本质上也是一个oc对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。
2.探寻block的本质
- 首先写一个简单的block
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^block)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
NSLog(@"this is a block!");
NSLog(@"this is a block!");
NSLog(@"this is a block!");
};
block(10, 10);
}
return 0;
}
3.查看其内部结构
使用命令行将代码转化为c++与OC代码进行比较
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令代码// 编译后代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int age = 10;
void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
}
return 0;
}
从以上c++代码中block的声明和定义分别与oc代码中相对应显示。将c++中block的声明和调用分别取出来查看其内部实现。
定义block变量代码// 定义block变量代码
void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
// 可以简化为下列
// void (*block)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age);
上述定义代码中,可以发现,block定义中调用了__main_block_impl_0函数,并且将__main_block_impl_0函数的地址赋值给了block。那么我们来看一下__main_block_impl_0函数内部结构。
__main_block_imp_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=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_imp_0结构体内有一个同名构造函数__main_block_imp_0,构造函数中对一些变量进行了赋值最终会返回一个结构体。
- 那么也就是说最终将一个__main_block_imp_0结构体的地址赋值给了block变量
- __main_block_impl_0结构体内可以发现__main_block_impl_0构造函数中传入了四个参数。(void *)__main_block_func_0、&__main_block_desc_0_DATA、age、flags。其中flage有默认值,也就说flage参数在调用的时候可以省略不传。而最后的 age(_age)则表示传入的_age参数会自动赋值给age成员,相当于age = _age。
- 接下来着重看一下前面三个参数分别代表什么。
(void *)__main_block_func_0参数```C++
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_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_1);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_3);
}
```
- 在__main_block_func_0函数中首先取出block中age的值,紧接着可以看到四个熟悉的NSLog,可以发现这段代码恰恰是我们在block块中写下的代码。
- 那么__main_block_func_0函数中其实存储着我们block中写下的代码。而__main_block_impl_0函数中传入的是(void *)__main_block_func_0,也就说将我们写在block块中的代码封装成__main_block_func_0函数,并将__main_block_func_0函数的地址传入了__main_block_impl_0的构造函数中保存在结构体内。
&__main_block_desc_0_DATA参数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)};
``` - 我们可以看到\_\_main\_block\_desc\_0中存储着两个参数,reserved和Block\_size,并且reserved赋值为0而Block\_size则存储着\_\_main\_block\_impl\_0的占用空间大小。最终将\_\_main\_block\_desc\_0结构体的地址传入\_\_main\_block\_func\_0中赋值给Desc。
age参数- age也就是我们定义的局部变量。因为在block块中使用到age局部变量,所以在block声明的时候这里才会将age作为参数传入,也就说block会捕获age,如果没有在block中使用age,这里将只会传入(void *)__main_block_func_0,&__main_block_desc_0_DATA两个参数。
这里可以根据源码思考一下为什么当我们在定义block之后修改局部变量age的值,在block调用的时候无法生效。 - 因为block在定义的之后已经将age的值传入存储在__main_block_imp_0结构体中并在调用的时候将age从block中取出来使用,因此在block定义之后对局部变量进行改变是无法被block捕获的。
- age也就是我们定义的局部变量。因为在block块中使用到age局部变量,所以在block声明的时候这里才会将age作为参数传入,也就说block会捕获age,如果没有在block中使用age,这里将只会传入(void *)__main_block_func_0,&__main_block_desc_0_DATA两个参数。
此时回过头来查看__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=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;// block 内部代码块地址
Desc = desc;// 存储block 对象占用的内存大小
}
};
首先我们看一下__block_impl第一个变量就是__block_impl结构体。
// __block_impl结构体内部
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
我们可以发现__block_impl结构体内部就有一个isa指针。因此可以证明block本质上就是一个oc对象。而在构造函数中将函数中传入的值分别存储在__main_block_impl_0结构体实例中,最终将结构体的地址赋值给block。
接着通过上面对__main_block_impl_0结构体构造函数三个参数的分析我们可以得出结论:
- __block_impl结构体中isa指针存储着&_NSConcreteStackBlock地址,可以暂时理解为其类对象地址,block就是_NSConcreteStackBlock类型的。
- block代码块中的代码被封装成__main_block_func_0函数,FuncPtr则存储着__main_block_func_0函数的地址。
- Desc指向__main_block_desc_0结构体对象,其中存储__main_block_impl_0结构体所占用的内存。
调用block执行内部代码
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
通过上述代码可以发现调用block是通过block找到FunPtr直接调用,通过上面分析我们知道block指向的是__main_block_impl_0类型结构体,但是我们发现__main_block_impl_0结构体中并不直接就可以找到FunPtr,而FunPtr是存储在__block_impl中的,为什么block可以直接调用__block_impl中的FunPtr呢?
重新查看上述源代码可以发现,(__block_impl *)block将block强制转化为__block_impl类型的,因为__block_impl是__main_block_impl_0结构体的第一个成员,相当于将__block_impl结构体的成员直接拿出来放在__main_block_impl_0中,那么也就说明__block_impl的内存地址就是__main_block_impl_0结构体的内存地址开头。所以可以转化成功。并找到FunPtr成员。
上面我们知道,FunPtr中存储着通过代码块封装的函数地址,那么调用此函数,也就是会执行代码块中的代码。并且回头查看__main_block_func_0函数,可以发现第一个参数就是__main_block_impl_0类型的指针。也就是说将block传入__main_block_func_0函数中,便于重中取出block捕获的值。
3.验证block的本质
- 验证block的本质是__main_block_impl_0结构体类型。
通过代码证明一下上述内容:
#import <Foundation/Foundation.h> struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}; struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
}; 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 {
int age = 10;
void (^block)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
NSLog(@"this is a block!");
NSLog(@"this is a block!");
NSLog(@"this is a block!");
};
// 将底层的结构体强制转化为我们自己写的结构体,通过我们自定义的结构体探寻block底层结构体
struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;
block(10, 10);
}
return 0;
}
通过打断点可以看出我们自定义的结构体可以被赋值成功,以及里面的值。

接下来断点来到block代码块中,看一下堆栈信息中的函数调用地址。Debuf workflow -> always show Disassembly

通过上图可以看到地址确实和FuncPtr中的代码块地址一样。
总结
- block的原理是怎样的?本质是什么?
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- block的底层结构如下图所示

iOS Block的本质(一)的更多相关文章
- iOS Block的本质(四)
iOS Block的本质(四) 上一篇文章iOS Block的本质(三)中已经介绍过block变量的捕获,本文继续探寻block的本质. 1. block内修改变量的值 int main(int ar ...
- # iOS Block的本质(三)
iOS Block的本质(三) 上一篇文章iOS Block的本质(二)中已经介绍过block变量的捕获,本文继续探寻block的本质. 1. block对对象变量的捕获,ARC 环境 block一般 ...
- iOS Block的本质(二)
iOS Block的本质(二) 1. 介绍引入block本质 通过上一篇文章Block的本质(一)已经基本对block的底层结构有了基本的认识,block的底层就是__main_block_impl_ ...
- IOS Block的本质
#import "HMViewController.h" #import "HMPerson.h" @interface HMViewController () ...
- iOS底层原理总结 - 探寻block的本质(一)
面试题 block的原理是怎样的?本质是什么? __block的作用是什么?有什么使用注意点? block的属性修饰词为什么是copy?使用block有哪些使用注意? block在修改NSMu ...
- iOS Block界面反向传值
在上篇博客 <iOS Block简介> 中,侧重解析了 iOS Block的概念等,本文将侧重于它们在开发中的应用. Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C ...
- iOS block从零开始
iOS block从零开始 在iOS4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调. block的结构 先来一段简单的代码看看: void ...
- iOS block 机制
本文要将block的以下机制,并配合具体代码详细描述: block 与 外部变量 block 的存储域:栈块.堆块.全局块 定义 块与函数类似,只不过是直接定义在另一个函数里,和定义它的那个函数共享同 ...
- ios Block详细用法
ios Block详细用法 ios4.0系统已开始支持block,在编程过程中,blocks被Obj-C看成是对象,它封装了一段代码,这段代码可以在任何时候执行.Blocks可以作为函数参数或者函数的 ...
随机推荐
- Sudo环境变量继承
sudo中默认配置会重置环境变量,所以使用sudo的时候需要小心这点.如何让sudo继承我们需要的环境变量?有如下两种方法: #sudo visudo 或者 #vi /etc/sudoers ...
- Python_两种导入模块的方法异同
Python中有两种导入模块的方法 1:import module 2:from module import * 使用from module import *方法可以导入独立的项,也可以用from m ...
- 【Hadoop】MapReduce笔记(三):MapReduce的Shuffle和Sort阶段详解
一.MapReduce 总体架构 整体的Shuffle过程包含以下几个部分:Map端Shuffle.Sort阶段.Reduce端Shuffle.即是说:Shuffle 过程横跨 map 和 reduc ...
- 201621123016《Java程序设计》第1周学习总结
1. 本周学习总结 本周的学习内容:java的发展历史,java程序设计环境,java简单语法. java与c++一样是一门面向对象的程序设计语言(相比于c++它是一门更彻底的面向对象的程序设计语言) ...
- Swift3.0 Set
set的简单的使用方法 //创建一个空set var letters = Set<Character>() //数组字面量创建set,只能存放string var setColors:Se ...
- gSoap学习笔记
http://www.cnblogs.com/xiangism/archive/2012/11/14/2770242.html http://www.cnblogs.com/lvkun/archive ...
- android摄像头获取图像——第三弹
相机获取图像的格式问题 android中承认的格式的参考网址为 :http://developer.android.com/reference/android/graphics/ImageFormat ...
- [Xcode 实际操作]二、视图与手势-(11)UITapGestureRecognizer手势之长按
目录:[Swift]Xcode实际操作 本文将演示使用视图的长按手势,完成视图的交互功能. import UIKit class ViewController: UIViewController { ...
- [Xcode 实际操作]八、网络与多线程-(9)使用异步Get方式获取网页源码
目录:[Swift]Xcode实际操作 本文将演示如何通过Get请求方式,异步获取网页源码. 异步请求与同步请求相比,不会阻塞程序的主线程,而会建立一个新的线程. 在项目导航区,打开视图控制器的代码文 ...
- JS与JQ的对比与提高
来吧, 案例1:先上个例子js写的省市二级联动 <!DOCTYPE html><html> <head> <meta charset="UTF-8& ...