格而知之16:我所理解的Block(2)
11、那么Block到底是怎么实现的呢?试一试通过将Block 的代码转换成普通C语言代码来查看它的实现过程。
要将OC代码转换成C语言代码,可以使用clang编译的一个命令:
通过这个命令能把指定文件中的OC代码改写成C++代码(其中主要部分还是普通的C语言代码),通过这些代码就能看到Block是如何使用C++语言实现的。
12、首先编写一个最简单的Block,没有返回值和参数列表,执行它会输出“Shayne Yeorg”。代码如下:
然后使用11的命令转换这个文件,可以得到文件main.cpp。main.cpp的内容很多,将其中相关的代码提取整理后如下:
这便是这个Block转换后的主要内容了。
13、第一次看到这段代码会觉得有点乱,毕竟使用的变量名虽然有规律但是比较长,为了更好地理解这段代码,试着将主要的命名替换成伪代码,如下:
这时候这段代码的内容就很清晰了:
(1)、它首先声明了一个struct来存放Block对象的基础结构(下文记为struct1),这个struct还不是完整的Block,只是组成部分,它主要包含了一个isa指针和一个函数指针FuncPtr;
(2)、然后声明了一个带有编号的具体函数(匿名函数),可以注意到函数里的内容其实就是源码中Block中要执行的内容了;
(3)、再声明一个struct用来存放Block对象的size等其他相关信息(下文记为struct2),同时定义一个此结构体的具体实例;
(4)、有了以上的准备后,就可以声明一个完整的Block对象的struct了,它由上面的struct1和struct2组成。
除此之外,还对这个完整的Block对象的struct声明了一个构造函数,使用这个构造函数不仅可以为其内部的struct2赋值,还能为struct1的函数成员FuncPtr赋值;
(5)、最后就是main函数了,在main函数内,首先调用完整Block对象的struct的构造函数定义了一个Block 对象,构造函数使用的参数是(2)的函数和(3)中声明的struct2的实例,达到了源码中定义Block的效果。
生成这个Block对象后,下一步就是调用这个Block对象内部的struct1的函数成员FuncPtr,达到了源码中调用这个Block的效果。
(6)、这就是一个最简单的Block对象的完整实现过程。
14、从13的实现过程可以分析出:
(1)、在定义FuncPtr函数的时候,可以注意到它是有参数的,参数是一个Block完整struct类型的变量__cself。
这类似于消息发送中,通过方法调用表找到对应方法的实现后,传递给方法实现的两个隐藏参数self和_cmd,在方法实现中通过self和_cmd就可以获取到objc_messageSend()函数的两个必要参数。
类似的,在这里,函数中通过__cself就可以访问到调用这个函数的Block本身了。
这个参数在13的例子中还没有用到,那只是一个最简单的Block,下文会涉及到使用这个参数的情况;
(2)、Block内部是有一个isa指针的,说明了它也是对象。并且在构造这个Block的时候,这个指针被指向了_NSConcreteStackBlock,关于这个将在下文详解;
(3)、Block的回调功能,本质上是通过调用函数实现的。
15、通过13的简单Block例子明白了Block的3个主要功能点之一的回调功能的实质,那么接下来来研究一下另一个功能点:截获变量。看一看有截获变量的Block 在转换为普通C语言后是什么样的。编写以下代码:
这段代码在定义Block之前先定义了两个变量,并且在Block中使用了这两个变量。执行这个Block 会打印出“Shayne Yeorg's shirt number is 10”。
16、使用clang命令重写代码,得到的C++代码中提取出的主要部分如下:
其中__block_impl结构的声明部分省略了,这部分是不会变的。
17、分析16的代码:
(1)、在Block的完整结构体__main_block_impl_0中,多了两个成员,成员的类型和命名和截获的变量name和shirtNum一样,并且它的构造函数也增加了对这两个成员变量的初始化;
(2)、这时__main_block_func_0函数就使用到__cself了,它通过__cself能获取到对应的__main_block_impl_0对象,然后就能取得两个成员变量name和shirtNum的值来使用了;
(3)、在main函数中,构造Block对象的时候,把定义的name和shirtNum作为参数传递进去了,所以在下一句代码调用funcPtr的时候,就能通过(2)的方法取得这两个变量的值了;
(4)、所以,Block截获变量的处理方法其实就是在其结构体内增加和截获变量相同类型的成员变量,然后将截获变量的值保存在成员变量里。这也就解释了,为什么一个变量被Block截获后还去修改它的值,也不会影响到Block中已截获到的值。
18、明白了Block的回调和截获变量的功能的实现方法后,再来看一下Block的最后一个功能:截获可变变量。
在Block里可以修改的变量,在声明的时候需要附上__block修饰符,这种变量和普通的变量是有很大区别的,首先来看一看在没有涉及到Block的情况下,__block变量的实质。编写以下代码来查看区别:
使用clang命令转换这段代码后,提取出主要部分如下:
19、在18中,可以注意到:
(1)、附上__block的变量,被转换成了一个全局的结构体,结构体中不仅包含了变量的值blockVar,还包含了一个__forwarding指针,从初始化的代码来看,这个指针指向了blockVar结构体自身;
(2)、修改blockVar变量的值的时候,并不是直接修改blockVar结构体中的blockVar成员的值,而是通过结构体中的__forwarding指针重新定位到blockVar结构体(在这里其实就是它自己),然后修改找到的这个blockVar结构体里的blockVar成员的值。
20、为什么修改修饰为__block的变量的值的时候不直接修改,要使用__forwarding指针绕了一圈回来修改呢?
这是因为在某些情况下(具体情况后文详解),截获了__block变量的Block会被从栈上复制到堆上,同时__block变量也会被复制一份到堆上,这时留在栈上的__block变量的__forwarding指针就会被置为指向堆上的__block变量。
这个时候,如果在Block之外使用这个__block变量,虽然使用的却仍是存放在栈上的变量,但是通过__forwarding指针却能顺利访问到堆上的__block变量,也就实现了Block内外访问的都是同一个__block变量的效果。
21、明白了__block变量的实质后,来看一看截获了可变变量的Block究竟是怎么实现的。编写以下代码:
(图J截获可变变量的Block1)
这段代码在Block中修改了__block变量的值,将这段代码进行转换:
22、分析21转换后的代码:
(1)、和18一样,__block变量shirtNum被转换成了一个全局结构体__Block_byref_shirtNum_0;
(2)、类似16的截获不可变变量的代码的处理方式,Block的完整结构体__main_block_impl_0也会增加一个__Block_byref_shirtNum_0结构体类型的成员shirtNum,通过构造函数可以看到它会被赋值为一个__Block_byref_shirtNum_0结构体的__forwarding成员指针;
(3)、在__main_block_func_0函数中,在通过__cself参数获取到__main_block_impl_0对象的shirtNum成员变量的值后,是通过shirtNum->__forwarding->shirtNum这样的方式去修改它的值的;
(4)、增加了__main_block_copy_0函数和__main_block_dispose_0函数,这两个函数的作用类似于NSObject的retain和release方法,只不过它们是用来管理Block对象的,用于Block对象从栈上复制到堆上以及从堆上废弃的时候;
(5)、在main函数中,通过定义__Block_byref_shirtNum_0结构体的对象,来达到定义__block变量的效果,这点也和18一样;
(6)、这就是截获了可变变量的Block的实质。
格而知之16:我所理解的Block(2)的更多相关文章
- 格而知之16:我所理解的Block(3)
23.在前文中的例子中,Block结构体里的isa指针还没有详细讲解,这个指针都被置向了_NSConcreteStackBlock,它标识了Block的类型. 其实除了_NSConcreteStack ...
- [转载]你需要知道的 16 个 Linux 服务器监控命令
转载自: 你需要知道的 16 个 Linux 服务器监控命令 如果你想知道你的服务器正在做干什么,你就需要了解一些基本的命令,一旦你精通了这些命令,那你就是一个 专业的 Linux 系统管理员. 有些 ...
- 格而知之3:Core Data的基本使用
最近准备做一个随手笔记类的app给自己用,考虑到从未使用过Core Data,就决定用Core Data来做数据存储.在网上参考了一些Core Data的资料后,用一天的时间写了这个demo,主要测试 ...
- 格而知之6:我所理解的Runtime(1)
基本简介 1.根据官方文档,OC有一个特性:它会尽可能把一些决定从编译时和链接时推迟到运行时才处理,所以这门语言需要的就不只是一个编译器,它还需要一个runtime系统来处理那些已经被编译过的代码. ...
- 格而知之15:我所理解的Block(1)
1.Block 本质上是一个struct结构体,在这个结构体中,最重要的成员是一个函数(当然除函数外还有其他重要的成员). 2.在开始解析Block之前,首先来回顾一下Block的格式.Block相关 ...
- 格而知之8:我所理解的Runtime(3)
关联对象 14.使用Category对类进行拓展的时候,只能添加方法,而不适合添加属性(可以添加属性,也可以正常使用get方法和set方法,只是不会自动生成以下划线开头命名的成员变量). 可以通过关联 ...
- 格而知之7:我所理解的Runtime(2)
消息发送(Messaging) 8.以上便是runtime相关的一些数据结构,接下来我们回看一开始的疑问: objc_msgSend()函数在执行的过程中是如何找到对应的类,找到对应的方法实现的呢? ...
- 格而知之5:我所理解的Run Loop
1.什么是Run Loop? (1).Run Loop是线程的一项基础配备,它的主要作用是来让某一条线程在有任务的时候工作.没有任务的时候休眠. (2).线程和 Run Loop 之间的关系是一一对应 ...
- Python程序员鲜为人知但你应该知道的16个问题(转)
add by zhj: 没找到原文出处,只能找到转载的,文中说有17个坑,其实是16个 全文如下 这篇文章主要介绍了Python程序员代码编写时应该避免的16个“坑”,也可以说成Python程序员代码 ...
随机推荐
- mysql 1449 : The user specified as a definer ('root'@'%') does not exist 解决方法
权限问题,授权 给 root 全部sql 权限 mysql> grant all privileges on *.* to root@"%" identified by & ...
- 利用Excel批量高速发送电子邮件
利用Excel批量高速发送电子邮件,分两步: 1. 准备待发送的数据: a.) 打开Excel,新建Book1.xlsx b.) 填入以下的内容, 第一列:接收人,第二列:邮件标题,第三列:正文,第四 ...
- c++读取ccbi
loader类文件: 需要定义CCB_STATIC_NEW_AUTORELEASE_OBJECT_METHOD(ButtonTestLayerLoader, loader); 这个宏定义是定义静态的l ...
- Unity怎样在Editor下运行协程(coroutine)
在处理Unity5新的AssetBundle的时候,我有一个需求,须要在Editor下(比方一个menuitem的处理函数中,游戏没有执行.也没有MonoBehaviour)载入AssetBundle ...
- TTB 基本
中文名 ,线程构建模块 外文名 Thread Building Blocks 缩 写 TBB 开 发 intel 目录 1线程构建模块 2黑体亮温 3斜交载重轮胎 4串联球轴承 1 ...
- .NET基础拾遗(3)字符串、集合和流2
二.常用集合和泛型 2.1 int[]是值类型还是引用类型? .NET中无论是存储值类型对象的数组还是存储引用类型的数组,其本身都是引用类型,其内存也都是分配在堆上的.所有的数组类型都继承自Syste ...
- MVC4过滤器(转)
先来看看一个例子演示过滤器有什么用: public class AdminController : Controller { // ... instance variables and constru ...
- MFC中使用ADO方式连接数据库
文章转自:http://blog.sina.com.cn/s/blog_a43aba5601014z8h.html 一.数据库操作准备 1.导入ADO动态链接库 在工程的stdafx.h中加入如下语句 ...
- 【JAVA编码专题】深入分析 Java 中的中文编码问题
http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/ 几种常见的编码格式 为什么要编码 不知道大家有没有想过一个问题,那就是为什么 ...
- 一键分享到新浪微博、腾讯微博、搜狐微博、人人网、开心网、百度收藏等js代码大全
下面给大家一些分享的js代码,只要把代码插入自己的网页中稍微修改一下图片路径就可以用了,好了,废话少说,上代码: document.writeln("<b>喜欢本文,那就分享到 ...