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 ...
随机推荐
- java 中判断字符串相等
今天写Java代码时遇到一个问题,就是关于判断两个字符串是否相等的问题.(刚尝试用SSH框架) 在大多编程中,通常比较两个字符串是否相同的表达式是“==”,但在java中不能这么写.在java中,用的 ...
- Spring Boot 学习系列(08)—自定义servlet、filter及listener
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 传统的filter及listener配置 在传统的Java web项目中,servlet.filter和li ...
- POJ1111【BFS】
在搜1011的时候误搜了1111,简单BFS吧,多一个X就是多四个面,每次看看他的四个面有多少个重复的,然后剪掉,最后答案加上就好了: code: //#include <bits/stdc++ ...
- ebullient(2018.10.25)
结论巨好想,每一次操作可以看作把一个b往前移一位,另一个b往后移一位,逆序对个数不改变,判断即可做第一问. 此处代码仅给出第一问做法: #include<cstdio> #include& ...
- 分布式通信-tcp/ip 广播
服务端 /** * 广播 */ public class MulticastServer { public static void main(String[] args) { try { //地址是2 ...
- 最近工作用到压缩,写一个zip压缩工具类
package test; import java.io.BufferedOutputStream;import java.io.File;import java.io.FileInputStream ...
- javascript 数组、json连接
json(或数组).concat(需要添加的json(或数组))
- mongodb vs redis(Tokyo Tyrant转)
* MongoDB vs Redis vs Tokyo Tyrant(原文链接:http://www.cnblogs.com/riceball/archive/2010/03/05/MongoDB_V ...
- AFHTTPSessionManager下载文件 及下载中 进度条处理,进度条处理需要特别注意,要加载NSRunLoop 中
1.下载文件 和进度条处理代码 - (void)timer:(NSTimer *)timer{ // 另一个View中 进度条progress属性赋值 _downloadView.progress = ...
- 如何解决netty发送消息截断问题
在netty开发过程中我遇到过长的消息被分割成多个小消息的问题.如下图所示: 其实这两条消息应该是一条消息,它们两个才是一个完整的json字符串.查看代码原来是客户端与服务器端都没有考虑TCP粘包 ...