深入Blocks分析
1.简介
从iOS4开始,苹果引入了这个C语言的扩充功能“Blocks”,在一些特定的场景下也是一把利刃。我前面一篇博客中初步介绍了Blocks这个东西,主要是语法的介绍(《iOS中Blocks的介绍》)。
我曾经看见了老外的一个系列的Blocks介绍,很有深度(A look inside blocks:Episode 1,A
look inside blocks:Episode 2, A look inside blocks:Episode 3),里面深入到汇编的层次对Blocks的实现进行了分析。不过如果象我这样对于汇编不熟悉的人肯定也是很多的,理解起来十分痛苦,于是就想到从ObjC本身对Blocks进行的处理里面来入手分析,看看对于Blocks都悄悄做了什么。
2.环境
很简单,就是Xcode啦。使用的编译器是CLang,主要是利用了-rewrite-objc这个参数,把源文件转换成中间文件。这样就揭开了面纱的一角。我使用的clang编译器版本是:
Apple clang version 4.0 (tags/Apple/clang-421.0.60) (based on LLVM 3.1svn)
Target: x86_64-apple-darwin12.5.0
Thread model: posix
转成中间文件的命令是:clang -rewrite-objc 源文件
3. 例子1
#include <stdio.h> int main(int argc, const char * argv[])
{
int val = 2;
int val1 = 5;
void (^blk)(void) = ^{printf("in Block():val=%d\n", val);}; val = 4;
blk();
printf("in main(): val=%d, val1=%d\n", val, val1);
return 0;
}
代码的运行结果是:
in Block():val=2
in main():val=4, val1=5
这里我们可以看到Block对于自动变量的“快照”功能。由于转成中间文件之后发现长了很多,变成了100多行(不同的clang版本转换出来的文件还不同,不过实现部分代码还是一样的),下面的代码是相关部分的节选,主要说明苹果是如何实现的。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
#include <stdio.h> int main(int, const char **); struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
printf("in Block():val=%d\n", val);} static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[])
{
int val = 2;
int val1 = 5;
void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val); val = 4;
((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
printf("in main(): val=%d, val1=%d\n", val, val1);
return 0;
}
中间文件确实看起来复杂了不少,不过还是有脉络可循。
看main函数的内容,里面有个函数指针blk,这个就是指向Block的指针,所以难怪Block变量的声明和函数指针如此相像(就是把*换成^),编译器转换后就是同一个东西啊。
我们看blk这个函数指针,就是__main_block_impl_0这个结构体变量的指针,这个结构体变量此时已经存在,然后用__main_block_func_0等几个变量赋初值。我们可以看到__main_block_impl_0这个struct中有个val这个项,并且在这里也赋值了,这就是给变量照的“快照”,由于这个变量在这里被记录了,所以无论外面的val变量如何变化,Block运行时使用的值就始终是“快照”的值了。同时我们也注意到__main_block_impl_0这个struct中没有val1这个项,所以说明如果Block中不用到的自动变量是不会自动加入到结构体中的。
Block的运行就是运行__main_block_impl_0这个struct中的FuncPtr这个指针,这个在前面初始化的时候已经被赋值成__main_block_func_0了,所以这里也就是运行这个函数,并把自己的指针传入。这里我们的实现非常简单,就是一句printf语句。
4.例子2
#include <stdio.h> int main(int argc, const char * argv[])
{
int __block val = 2;
int val1 = 5;
void (^blk)(void) = ^{printf("in Block():val=%d\n", ++val);}; blk();
printf("in main(): val=%d, val1=%d\n", val, val1);
return 0;
}
这个例子把自动变量val声明成了__block变量,这样语法上Block不是对val进行“快照”,而是会直接使用val变量,同时在Block内部val可读可写,不再是只读的了。
运行结果如下:
in Block():val=3
in main(): val=3, val1=5
同样展开成中间文件来看苹果的实现。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
#include <stdio.h> int main(int, const char **);
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
}; struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
printf("in Block():val=%d\n", ++(val->__forwarding->val));}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);} static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long 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};
int main(int argc, const char * argv[])
{
__Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 2};
int val1 = 5;
void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (struct __Block_byref_val_0 *)&val, 570425344); ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
printf("in main(): val=%d, val1=%d\n", (val.__forwarding->val), val1);
return 0;
}
中间文件又变长了一些,除去我们已经了解的部分,我们可以看到自动变量val不再是直接加入到__main_block_impl_0里面,而是又变成了一个__Block_byref_val_0的struct结构体的指针。
main函数里面对于val的赋值已经变成了对这样一个数据结构的赋值,第一句上就把2赋给了__Block_byref_val_0里面的val项,然后在blk这个指针初始化的时候,把__Block_byref_val_0的结构体变量指针传入__main_block_impl_0。此后所有对于自动变量val的操作都变成对val.__forwarding->val的操作。这样就解决了Block内外变量同时变化的问题(在操作同一块内存)。
这里还看见__Block_byref_val_0里面有个__forwarding项,这个项是指向自身的一根指针。在blk指针初始化的时候我们把这个指针的值传入了__main_block_impl_0。
在__main_block_desc_0里面,多出了两个函数指针,分别用于copy和dispose,这两个函数这里也是自动生成的。
5.总结
综合前面的例子来看,Block的实现还是借助了C语言的函数指针来实现了,对于普通的自动变量,在Block声明时会快照内容存储;对于__block变量,则是生成一个数据结构来存储,然后取代所有访问这个变量的地方。
事实上,因为Block运行时完全可能自动变量的生命周期已经结束,所以Block对于内存的管理是很复杂的,会把内容从栈上copy到堆上(这也是copy和dispose函数的作用)。所以Block虽然威力巨大,但使用时也需要遵循一定的规则。
深入Blocks分析的更多相关文章
- iOS - Blocks
iOS中Blocks的介绍 1. 什么是Blocks Blocks是C语言的扩充功能.就是:带有自动变量的匿名函数. 类似C语言的函数指针.但Blocks不是一个指针,而是一个不带名字的函数, ...
- Code:Blocks 中文乱码问题原因分析和解决方法
下面说说修改的地方. 1.修改源文件保存编码在:settings->Editor->gernal settings 看到右边的Encoding group Box了吗?如下图所示: Use ...
- LLVM程序分析日记之 basic blocks could have duplicate predecessors
We used the predecessors() to get the predecessors of a basic block based on LLVM's IR. The code is ...
- 【JUC】JDK1.8源码分析之SynchronousQueue(九)
一.前言 本篇是在分析Executors源码时,发现JUC集合框架中的一个重要类没有分析,SynchronousQueue,该类在线程池中的作用是非常明显的,所以很有必要单独拿出来分析一番,这对于之后 ...
- 统计和分析系统性能【IO CPU 内存】的工具集合
统计和分析系统性能[IO CPU 内存]的工具集合 blktrace http://www.oschina.net/p/blktrace 获取磁盘写入的信息 root@demo:~/install/p ...
- cocos2dx骨骼动画Armature源码分析(一)
源码分析一body { font-family: Helvetica, arial, sans-serif; font-size: 14px; line-height: 1.6; padding-to ...
- 使用logminer挖掘日志,分析历史操作 系列一
===============STARTED==================================== 事件起因: 业务的人mail过来说是有张表记录的10K+的优惠码记录没了,要我们确 ...
- 关于一个sql的优化分析
应用这边新上线了一个查询,正在跑,让我看下状态以及分析下能不能再快点. 如下sql: SELECT x.order_no , order_date+7/24 AS order_date, addres ...
- delete语句跑了3个小时分析以及关于并行的一些知识
=====================START==================================== 闲来无事,看了下数据库跑的long running sql, SQL> ...
随机推荐
- pycharm的激活
——————转———— Pycharm5注册方式 0x1 ,安装 0x2 , 调整时间到2038年. 0x3 ,申请30天试用 0x4, 退出pycharm 0x5, 时间调整回来. ##注册方法 ...
- leetcode算法刷题(五)——动态规划(三)
今天的题目不是leetcode上面的.只是觉得动态规划还是不算很熟练,就接着找了点DP的题练练 最长递增子序列的长度 题目的意思:传入一个数组,要求出它的最长递增子序列的长度.例如:如在序列1,-1, ...
- ffmpeg在Win7 VS2010中debug通过,release出错的问题解决方法
我所用的系统环境是Win7 32位操作系统+VS2010编译环境.所以在debug模式下调通之后,在Release模式下调试不通过,最后通过上网查资料和自己对比两个编译选项得出以下结论: 修改“项目- ...
- MVC 返回ActionResult 类型
1. Content() 返回文本类型,比如"Hello World!". 2.File() 返回文件类型,如PDF,JPG. 3.HttpNotFound() 返回404 HTT ...
- Google机器学习教程心得(二)决策树与可视化
Visualizing a Decision Tree Google Machine Learning Recipes 2 官方中文博客 http://chinagdg.org/2016/03/mac ...
- SQL Server 分离与附加数据库
前期准备: 创建数据库 create database Studio on primary (name ='Studio',filename ='E:\Studio.mdf'), ...
- Mining 影响数据挖掘结果的 5 方面
第一个: 数据类型. 对象的不同属性会用不同的数据类型来描述,如 年龄-->int; 生日 -->date;数据挖掘时也要对不同的类型有不同的对待. 第二个: 数据质量. 数据质量直接影 ...
- overfitting
当模型复杂度上升时,可控制参数变多,VC dimension变大, 当VC dimension过大时,虽然Ein很小,但是Eout很大,产生overfitting 比喻:开车 开太快 ...
- How to convert string to wstring?
How to convert string to wstring? - Codejie's C++ Space - C++博客 How to convert string to wstring ...
- JAVA之数组查询binarySearch()方法详解
binarySearch()方法提供了多种重载形式,用于满足各种类型数组的查找需要,binarySearch()有两种参数类型 注:此法为二分搜索法,故查询前需要用sort()方法将数组排序,如果数组 ...