block总结
3.编译器中的block
3.1 block的数据结构定义
我们通过大师文章中的一张图来说明:

上图这个结构是在栈中的结构,我们来看看对应的结构体定义:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *);};struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */}; |
从上面代码看出,Block_layout就是对block结构体的定义:
isa指针:指向表明该block类型的类。
flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等。
reserved:保留变量,我的理解是表示block内部的变量数。
invoke:函数指针,指向具体的block实现的函数调用地址。
descriptor:block的附加描述信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。
variables:因为block有闭包性,所以可以访问block外部的局部变量。这些variables就是复制到结构体中的外部局部变量或变量的地址。
3.2 block的类型
block有几种不同的类型,每种类型都有对应的类,上述中isa指针就是指向这个类。这里列出常见的三种类型:
_NSConcreteGlobalBlock:全局的静态block,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:
|
1
2
3
4
5
|
#include int main(){ ^{ printf("Hello, World!\n"); } (); return 0;} |
_NSConcreteStackBlock:保存在栈中的block,当函数返回时被销毁。例如:
|
1
2
3
4
5
6
|
#include int main(){ char a = 'A'; ^{ printf("%c\n",a); } (); return 0;} |
_NSConcreteMallocBlock:保存在堆中的block,当引用计数为0时被销毁。该类型的block都是由_NSConcreteStackBlock类型的block从栈中复制到堆中形成的。例如下面代码中,在exampleB_addBlockToArray方法中的block还是_NSConcreteStackBlock类型的,在exampleB方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
void exampleB_addBlockToArray(NSMutableArray *array) { char b = 'B'; [array addObject:^{ printf("%c\n", b); }];}void exampleB() { NSMutableArray *array = [NSMutableArray array]; exampleB_addBlockToArray(array); void (^block)() = [array objectAtIndex:0]; block();} |
总结一下:
_NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,我理解为它可能在内存的全局区。
_NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block只且只有有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了,所以无法多次使用。
_NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。
3.3 编译器如何编译
我们通过一个简单的示例来说明:
|
1
2
3
4
5
6
7
8
9
10
11
|
#import typedef void(^BlockA)(void);__attribute__((noinline))void runBlockA(BlockA block) { block();}void doBlockA() { BlockA block = ^{ // Empty block }; runBlockA(block);} |
上面的代码定义了一个名为BlockA的block类型,该block在函数doBlockA中实现,并将其作为函数runBlockA的参数,最后在函数doBlockA中调用函数runBloackA。
注意:如果block的创建和调用都在一个函数里面,那么优化器(optimiser)可能会对代码做优化处理,从而导致我们看不到编译器中的一些操作,所以用__attribute__((noinline))给函数runBlockA添加noinline,这样优化器就不会在doBlockA函数中对runBlockA的调用做内联优化处理。
我们来看看编译器做的工作内容:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#import __attribute__((noinline))void runBlockA(struct Block_layout *block) { block->invoke();}void block_invoke(struct Block_layout *block) { // Empty block function}void doBlockA() { struct Block_descriptor descriptor; descriptor->reserved = 0; descriptor->size = 20; descriptor->copy = NULL; descriptor->dispose = NULL; struct Block_layout block; block->isa = _NSConcreteGlobalBlock; block->flags = 1342177280; block->reserved = 0; block->invoke = block_invoke; block->descriptor = descriptor; runBlockA(&block);} |
上面的代码结合block的数据结构定义,我们能很容易得理解编译器内部对block的工作内容。
3.4 copy()和dispose()
上文中提到,如果我们想要在以后继续使用某个block,就必须要对该block进行拷贝操作,即从栈空间复制到堆空间。所以拷贝操作就需要调用Block_copy()函数,block的descriptor中有一个copy()辅助函数,该函数在Block_copy()中执行,用于当block需要拷贝对象的时候,拷贝辅助函数会retain住已经拷贝的对象。
既然有有copy那么就应该有release,与Block_copy()对应的函数是Block_release(),它的作用不言而喻,就是释放我们不需要再使用的block,block的descriptor中有一个dispose()辅助函数,该函数在Block_release()中执行,负责做和copy()辅助函数相反的操作,例如释放掉所有在block中拷贝的变量等。
4.总结
block总结的更多相关文章
- Objective-C中block的底层原理
先出2个考题: 1. 上面打印的是几,captureNum2 出去作用域后是否被销毁?为什么? 同样类型的题目: 问:打印的数字为多少? 有人会回答:mutArray是captureObject方法的 ...
- iOS 键盘添加完成按钮,delegate和block回调
这个是一个比较初级一点的文章,新人可以看看.当然实现这个需求的时候自己也有一点收获,记下来吧. 前两天产品要求在工程的所有数字键盘弹出时,上面带一个小帽子,上面安装一个“完成”按钮,这个完成按钮也没有 ...
- python中IndentationError: expected an indented block错误的解决方法
IndentationError: expected an indented block 翻译为IndentationError:预期的缩进块 解决方法:有冒号的下一行要缩进,该缩进就缩进
- JDBC Tutorials: Commit or Rollback transaction in finally block
http://skeletoncoder.blogspot.com/2006/10/jdbc-tutorials-commit-or-rollback.html JDBC Tutorials: Com ...
- 嵌入式&iOS:回调函数(C)与block(OC)传 参/函数 对比
C的回调函数: callBack.h 1).声明一个doSomeThingCount函数,参数为一个(无返回值,1个int参数的)函数. void DSTCount(void(*CallBack)(i ...
- 嵌入式&iOS:回调函数(C)与block(OC)回调对比
学了OC的block,再写C的回调函数有点别扭,对比下区别,回忆记录下. C的回调函数: callBack.h 1).定义一个回调函数的参数数量.类型. typedef void (*CallBack ...
- Block解析(iOS)
1. 操作系统中的栈和堆 我们先来看看一个由C/C++/OBJC编译的程序占用内存分布的结构: 栈区(stack):由系统自动分配,一般存放函数参数值.局部变量的值等.由编译器自动创建与释放.其操作方 ...
- CSS学习笔记——包含块 containing block
以下内容翻译自CSS 2.1官方文档.网址:https://www.w3.org/TR/CSS2/visudet.html#strut 有时,一个元素的盒子的位置和尺寸根据一个确定的矩形计算,这个确定 ...
- 用block做事件回调来简化代码,提高开发效率
我们在自定义view的时候,通常要考虑view的封装复用,所以如何把view的事件回调给Controller就是个需要好好考虑的问题, 一般来说,可选的方式主要有target-action和de ...
- 关于多个block问题
在某个添加文本的页面中,leftbarbutton是删除(直接将数组中的这个string删除),rightbarbutton是完成,分别对应两个block,完成的block是一开始写的,写到了view ...
随机推荐
- 2.3-2.6 HBase java API
一.get .put.delete.scan 1.代码 package com.beifeng.senior.hadoop.hbase; import org.apache.hadoop.conf.C ...
- 微信小程序开发之页面注册
页面Page是object Page({ data:{ String1 }, onLoad:function(options){ // 生命周期函数--监听页面加载 一个页面只会调用 ...
- flex设置默认字体为微软雅黑
必须使用英文名称 Microsoft YaHei 否则有些系统不识别
- Linux文件IO操作函数概述
文件概述 Linux中,一切皆文件.文件为操作系统服务和设备提供了一个简单而一致的接口.这意味着程序完全可以像使用文件那样使用磁盘文件.串行口.打印机和其他设备. 也就是说,大多数情况下,你只需要使用 ...
- 201621123016 《Java程序设计》第九周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 2. 书面作业 本次作业题集集合 1. List中指定元素的删除(题集题目) 1.1 实验总结.并回答:列举至 ...
- CodeForces 723F【DFS瞎搞】
题意: 给你一幅图,你要用这些边构造一个树, s和t两个节点的度数不能超过ds dt 而且图是保证没有环 思路: 树的性质是:无环(已经保证),无向(保证),连通(还要判断) 首先把S,T点从图里剥离 ...
- iOS集成支付宝支付
本文由本人原创发表于博客园,转载请注明出处 http://www.cnblogs.com/wangqw/p/5074907.html 一. 开发前准备 iOS 支付宝SDK下载地址:(内含iOS An ...
- luoguP2408不同子串个数
传送门 可以知道每一个子串都是后缀的前缀,那么对于第\(i\)小的后缀的贡献就可以表示为n-sa[i]+1 然而会存在重复的子串,注意height数组的定义,对于sa[i-1]和sa[i],只有hei ...
- jq解析json文件
$.ajaxSettings.async = false;//同步还是异步 $.getJSON(URL,function(data){ //成功后执行 })
- VMware虚拟机文件(后缀)详解
VMware虚拟机文件(后缀)详解 虚拟机的文件管理由VMware Workstation来执行,一个虚拟机一般以一系列文件的形式储存在宿主机中,这些文件一般在由workstation为虚拟机所创建的 ...