Block学习-关于Block是如何实现的,以及block中参数传递
先看下面的一段代码,判断输出结果:
int a = 0;
void (^block)(void) = ^{printf("a = %i\n",a);};
a=10;
block();
经过运行之后我们发现,输出结果为“a = 0”,而不是"a = 10"。
如果感觉答案是“a = 10”的同学看下面的代码或许会有所感觉,明显会报错,提示“Use of undeclared identifier 'a' ”。
void (^block)(void) = ^(){printf("a = %i\n",a);};
int a=10;
block();
我们发现block块在初始化的时候就要获取局部变量(局部变量)a的值,之后a的值的变化并不会影响block的运行结果。
*所说的局部变量为block外的局部变量,而非block内定义的。
Block结构:
到底block是如何实现的呢?下面是一个最简单的block的定义与使用
int main(int argc, const char * argv[]) {void (^block)(void) = ^{printf("This is a block!");};block();
}
通过终端,我们找到main.m文件所在路径,然后输入
clang -rewrite-objc main.m
(通过clang编译器,我们可以获得在编译过程中生产的中间代码,看block时如何实现的)
在当前目录下找到main.cpp文件,打开后能看到block生成了很多结构体如下:(简化后代码,可查找关键字),
struct __block_impl {void *isa;//block存放位置,取值为_NSConcretGlobalBlock(全局区)、_NSConcretStackBlock(栈区)、_NSConcretMallocBlock(堆区)int Flags;//用于按bit位表示一些block的附加信息,block copy的实现代码可以看到对该变量的使用。int Reserved;//保留变量。void *FuncPtr;
};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)};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("This is a block!");
}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;}
};int main(int argc, const char * argv[]) {void (*block)(void) = (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);
}
看上去很多,其实没那么难理解,一个一个来分别介绍他们的意思:
__block_impl:block的一些基础属性,像是block的基类。
__main_block_desc_0:block的描述,他有一个实例__main_block_desc_0_DATA
__main_block_impl_0:block变量(包含上面两个结构体对象)。
__main_block_func_0:block的匿名函数。存放block内的语句。
命名规则:“main”为block所在函数名,如果定义在函数外为block的名字,末尾的“0”表示为当前函数中第“0”个block。
在末尾的main函数中,我们可以看到block的初始化是调用__main_block_impl_0的构造函数,所以block的内容简化后为:
struct __main_block_impl_0 {void *isa;int Flags;int Reserved;void *FuncPtr;size_t reserved;size_t Block_size;__main_block_impl_0(#block匿名函数的指针#,#__main_block_desc_0的实例指针#, int flags=0);
};
由此可见:block其实就是一个Object-c对象。
局部变量:
通过clang -rewrite-objc 指令编译本文开头的那段代码我们找出与之前的编译结果的不同:
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 copyprintf("a = %i\n",a);
}
//主方法
int main(int argc, const char * argv[]) {int a = 0;//block初始化void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);a=10;//block调用((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}</span>
比较后发现:
__main_block_impl_0的属性中多了一个变量a;
__main_block_impl_0初始化方法的参数中也有接收变量对a赋值的方法。
__main_block_func_0而在匿名函数中多了一行“int a = __cself->a;”
局部变量的传递的实现方法,当block在初始化的时候,通过构造函数把a的值通过值传递传入block,而在__main_block_func_0方法中,通过int a创建变量来接收。最后在匿名函数中使用时又创建临时变量类获得a。所以在block执行时,输出的a为block初始化时获得的a的值,使用的是a的副本。
这也能说明为什么,block中不让修该局部变量。首先在block中修改局部变量的值本是没问题的,但是修改的只是和局部变量名字一样的副本变量的值,这并不会影响局部变量本来的值。为了不让开发者迷惑,所以就不让赋值。
那么如果获取的局部变量是OC对象呢
int main(int argc, const char * argv[]) {NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:0];void (^block)(void) = ^{[array addObject:@"block"];};block();
}
这么写是能运行的,因为array是一个指针,我们并没有改变指针的值。
static修饰符与全局变量
全局变量或静态变量都在全局区,在内存中的地址是固定的,Block在初始化时读取的是该变量值地址,当调用的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。而且可以在block中修改他们的值。
int a=0;
int main(int argc, const char * argv[]) {static int b=0;void (^block)(void) = ^{NSLog(@"a=%i,b=%i",++a,++b);};a = 10;b = 10;block();
}
运行的结果为(不是“a=1,b=1”):
a=11,b=11
__block修饰符
之前了解了block中用到局部变量时,使用的是局部变量的副本,当我们要想局部变量能在block中进行修改时,我们通常会在局部变量前写上__block修饰符,那么它又是如何实现的呢。对如下代码进行编译:
int main(int argc, const char * argv[]) {__block int a=0;void (^block)(void) = ^{printf("a = %i",++a);};block();
}
编译后的代码简化为(只列出有变化的部分)
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 refprintf("a = %i",++(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*/);}int main(int argc, const char * argv[]) {__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
我们发现多了很多东西,看上去很乱,我们一个一个来,首先我们看主函数main里第一行,由__block int a=0;编译而来。当我们为局部变量加上__block修饰符时,编译器会自动创建一个关于这个变量的结构体__Block_byref_a_0,而变量的初始化,变为结构体的初始化,将局部变量对象化了。
在关于变量a的结构体__Block_byref_a_0中,有“int a;”用于存放变量值,有“__Block_byref_a_0 *__forwarding;”一个关于自己的指针,以及其他一些属性。而block初始化时,将对象化后的a作为了构造函数的参数。由之前分析我们已经得知如果block获取的局部变量是OC对象,是可以对它进行操作的。
为什么不直接创建变量a的指针
到这里应该理解了__block的实际工作,但是肯定会有人疑惑,说到底,将a对象化的目的只是因为对象传递得时候传的是指针,那么为什么不直接创建一个指针指向变量a,而要将他对象化。说到这里就要先说说内存中栈和堆,函数内局部变量的初始化是分配到栈中,而对象的初始化是分配在堆中(ios中alloc都是存放在堆中),变量a是分配在栈中,如果创建一栈变量的指针很危险,栈相对于堆而言是编译器自动释放,当函数结束时,栈就会被释放,而block对象通常分配在堆上,还没有被释放,而这时候原来的指针就会变成野指针,对block而言是不安全的,
初始 | copy后 | |
arc | 堆 | 堆 |
非arc | 栈 | 堆 |
block对局部变量与对象的区分
在之前的编译结果中,还多出了两个方法:
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*/);
}
block会持有捕获的对象,包括局部变量(加了__block修饰符的)和oc对象,编译器通过BLOCK_FIELD_IS_BYREF和BLOCK_FIELD_IS_OBJECT来区分。
上面两个方法的最后一个参数都为8,表示的是BLOCK_FIELD_IS_BYREF。分别为局部变量的copy方法与释放方法。
Block学习-关于Block是如何实现的,以及block中参数传递相关推荐
- 深度学习论文阅读目标检测篇(七)中英对照版:YOLOv4《Optimal Speed and Accuracy of Object Detection》
深度学习论文阅读目标检测篇(七)中英对照版:YOLOv4<Optimal Speed and Accuracy of Object Detection> Abstract 摘要 1. In ...
- Cadence Allegro 17.4学习记录开始25-PCB Editor 17.4软件PCB中布线操作基础
目录 Cadence Allegro 17.4学习记录开始25-PCB Editor 17.4软件PCB中布线操作基础 一.走线和修线 走线操作步骤: 修线操作步骤: 二.Copy操作 三.chang ...
- 【深度学习】利用神网框架分割病理切片中的癌组织(胃)
[深度学习]利用神网框架分割病理切片中的癌组织(胃) 文章目录 1 数据描述 2 思路 3 准备数据 4 构建模型 5 模型优化 6 程序执行 7 观察结果 1 数据描述 初赛选取癌病理切片,为常规 ...
- oracle修改asm参数文件,学习笔记:Oracle RAC参数文件管理 修改创建asm中的spfile文件...
天萃荷净 Oracle rac创建修改asm中的spfile文件内容 create spfile to asm --查看sid SQL> show parameter instance_name ...
- 【Rust 日报】2021-11-11 保持冷静,学习Rust,我们很快就会在Linux中更多的看到这种语言...
为Rust有很多字符串类型而烦恼吗?好吧,它没有必要 众所周知Rust有几种不同的字符串类型.两个主要的竞争者是: &str是一个 "字符串引用".它是不可调整大小的,它的 ...
- 微信小程序学习(三):在微信开发者工具中,使用WeUI前端美化框架,微信小程序
微信小程序学习(三):在微信开发者工具中,使用WeUI 这里就是将WeUI导入到微信开发者工具中,我并没有使用多少样式,这里只是展示了一下怎么引用 网上有很多的方法,我绝大多数都没看懂,这里就是最简单 ...
- 百词斩英语单词小助手(主要实现英语单词学习的功能。用户可对词典文件中的单词进行预览,增删改查。同时还可进行中英、英中测试。本系统还提供了测试成绩的显示功能。)
struct word //单词的结构体 {char en[MAX_CHAR]; // 英文形式char ch[MAX_CHAR]; //中文形式 } s[MAX_NUM]; //单词数组 int n ...
- 【杂谈】万字长文回顾深度学习的崛起背景,近10年在各行各业中的典型应用
笔者作为一个从业5年多的技术人员,吃到了深度学习的早期红利,这次来聊一聊深度学习的崛起背景.当下的典型应用领域,算作给尚未或者正打算拥抱这门技术的朋友们一个较为全面的科普. 深度学习为什么能够崛起 一 ...
- Bootstrap前端框架学习(一):Bootstrap在Vue项目中的安装及可视化布局
Bootstrap前端框架学习(一):Bootstrap在Vue项目中的安装及可视化布局 node.js的安装 bootstrap的安装 bootstrap快速布局 node.js的安装 我们需要安装 ...
最新文章
- vscode 高效使用指南
- linux 服务器 安装网卡驱动,linux下安装编译网卡驱动的方法
- Seata RPC 模块的重构之路
- 高动态范围图像HDR
- Web负载均衡学习笔记之四层和七层负载均衡的区别
- apache php url重写语法,apache url重写实现伪静态
- java scanner_Java Scanner nextLong()方法与示例
- ORACLE删除当前用户下所有的表的方法
- tnt_esri.dat Arcgis8.1安装license
- VM虚拟机,Linux系统安装tools过程遇到 what is the location of the “ifconfig” program
- BZOJ5212: [Zjoi2018]历史
- 【语音合成】基于matlab语音信号变速【含Matlab源码 565期】
- X86架构基本汇编指令详解
- 下载频道2013上半年超人气精华资源汇总
- VxWorks的漏洞分析与解决方案
- 用java实现电脑的usb功能,包括鼠标,键盘
- jsp页面hidden的诡异
- python技术分享_Python技术分享:爬虫
- 强制删除正在运行的文件
- 【python 图片文字识别】pyocr图片文字识别