iOS中block实现的探究
[0. Brief introduction of block]
Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展,用来实现匿名函数的特性。
用维基百科的话来说,Block是Apple Inc.为C、C++以及Objective-C加入的特性,使得这些语言能够用类lambda表达式的语法来创建闭包。
用Apple文档的话来说,A block is an anonymous inline collection of code, and sometimes also called a "closure".
关于闭包,我认为阮一峰的一句话解释简洁明了:闭包就是可以读取其他函数内部变量的函数。
这个解释用到block来也非常恰当:一个函数里定义了个block,这个block能够訪问该函数的内部变量。
一个简单的Block示比例如以下:
- int (^maxBlock)(int, int) = ^(int x, int y) { return x > y ? x : y; };
假设用Python的lambda表达式来写,能够写成例如以下形式:
- f = lambda x, y : x if x > y else y
只是由于Python自身的语言特性,在def定义的函数体中,能够非常自然地再用def语句定义内嵌函数,由于这些函数本质上都是对象。
假设用BNF来表示block的上下文无关文法,大致例如以下:
- block_expression ::= ^ block_declare block_statement
- block_declare ::= block_return_type block_argument_list
- block_return_type ::= return_type | 空
- block_argument_list ::= argument_list | 空
[1. Why block]
Block除了可以定义參数列表、返回类型外,还可以获取被定义时的词法范围内的状态(比方局部变量),而且在一定条件下(比方使用__block变量)可以改动这些状态。此外,这些可改动的状态在同样词法范围内的多个block之间是共享的,即便出了该词法范围(比方栈展开,出了作用域),仍可以继续共享或者改动这些状态。
通常来说,block都是一些简短代码片段的封装,适用作工作单元,通经常使用来做并发任务、遍历、以及回调。
比方我们能够在遍历NSArray时做一些事情:
- - (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
当中将stop设为YES,就跳出循环,不继续遍历了。
而在非常多框架中,block越来越常常被用作回调函数,代替传统的回调方式。
- 用block作为回调函数,能够使得程序猿在写代码更顺畅,不用中途跑到还有一个地方写一个回调函数,有时还要考虑这个回调函数放在哪里比較合适。採用block,能够在调用函数时直接写兴许处理代码,将其作为參数传递过去,供其任务运行结束时回调。
- 还有一个优点,就是採用block作为回调,能够直接訪问局部变量。比方我要在一批用户中改动一个用户的name,改动完毕后通过回调更新相应用户的单元格UI。这时候我须要知道相应用户单元格的index,假设採用传统回调方式,要嘛须要将index带过去,回调时再回传过来;要嘛通过外部作用域记录当前操作单元格的index(这限制了一次仅仅能改动一个用户的name);要嘛遍历找到相应用户。而使用block,则能够直接訪问单元格的index。
这份文档中提到block的几种适用场合:
- 任务完毕时回调处理
- 消息监听回调处理
- 错误回调处理
- 枚举回调
- 视图动画、变换
- 排序
[2. About __block_impl]
Clang提供了中间代码展示的选项供我们进一步了解block的原理。
以一段非常easy的代码为例:
使用-rewrite-objc选项编译:
得到一份block0.cpp文件,在这份文件里能够看到例如以下代码片段:
从命名能够看出这是block的实现,而且得知block在Clang编译器前端得到实现,能够生成C中间代码。非常多语言都能够仅仅实现编译器前端,生成C中间代码,然后利用现有的非常多C编译器后端。
从结构体的成员能够看出,Flags、Reserved能够先略过,isa指针表明了block能够是一个NSObject,而FuncPtr指针显然是block相应的函数指针。
由此,揭开了block的神奇面纱。
只是,block相关的变量放哪里呢?上面提到block能够capture词法范围内(或者说是外层上下文、作用域)的状态,即便是出了该范围,仍然能够改动这些状态。这是怎样做到的呢?
[3. Implementation of a simple block]
先看一个仅仅输出一句话的block是怎么样的。
生成中间代码,得到片段例如以下:
首先出现的结构体就是__main_block_impl_0,能够看出是依据所在函数(main函数)以及出现序列(第0个)进行命名的。假设是全局block,就依据变量名和出现序列进行命名。__main_block_impl_0中包括了两个成员变量和一个构造函数,成员变量各自是__block_impl结构体和描写叙述信息Desc,之后在构造函数中初始化block的类型信息和函数指针等信息。
接着出现的是__main_block_func_0函数,即block相应的函数体。该函数接受一个__cself參数,即相应的block自身。
再以下是__main_block_desc_0结构体,当中比較有价值的信息是block大小。
最后就是main函数中对block的创建和调用,能够看出运行block就是调用一个以block自身作为參数的函数,这个函数相应着block的运行体。
这里,block的类型用_NSConcreteStackBlock来表示,表明这个block位于栈中。相同地,还有_NSConcreteMallocBlock和_NSConcreteGlobalBlock。
因为block也是NSObject,我们能够对其进行retain操作。只是在将block作为回调函数传递给底层框架时,底层框架须要对其copy一份。例如说,假设将回调block作为属性,不能用retain,而要用copy。我们一般会将block写在栈中,而须要回调时,往往回调block已经不在栈中了,使用copy属性能够将block放到堆中。或者使用Block_copy()和Block_release()。
[4. Capture local variable]
再看一个訪问局部变量的block是如何的。
生成中间代码,得到片段例如以下:
能够看出这次的block结构体__main_block_impl_0多了个成员变量i,用来存储使用到的局部变量i(值为1024);而且此时能够看到__cself參数的作用,类似C++中的this和Objective-C的self。
假设我们尝试改动局部变量i,则会得到例如以下错误:
错误信息非常具体,既告诉我们变量不可赋值,也提醒我们要使用__block类型标识符。
为什么不能给变量i赋值呢?
由于main函数中的局部变量i和函数__main_block_func_0不在同一个作用域中,调用过程中仅仅是进行了值传递。当然,在上面代码中,我们能够通过指针来实现局部变量的改动。只是这是由于在调用__main_block_func_0时,main函数栈还没展开完毕,变量i还在栈中。可是在非常多情况下,block是作为參数传递以供兴许回调运行的。通常在这些情况下,block被运行时,定义时所在的函数栈已经被展开,局部变量已经不在栈中了(block此时在哪里?),再用指针訪问就⋯⋯。
所以,对于auto类型的局部变量,不同意block进行改动是合理的。
[5. Modify static local variable]
于是我们也能够判断出,静态局部变量是怎样在block运行体中被改动的——通过指针。
由于静态局部变量存在于数据段中,不存在栈展开后非法訪存的风险。
上面中间代码片段与前一个片段的区别主要在于main函数里传递的是i的地址(&i),以及__main_block_impl_0结构体中成员i变成指针类型(int
*)。
然后在运行block时,通过指针改动值。
当然,全局变量、静态全局变量都能够在block运行体内被改动。更准确地讲,block能够改动它被调用(这里是__main_block_func_0)时所处作用域内的变量。比方一个block作为成员变量时,它也能够訪问同一个对象里的其他成员变量。
[6. Implementation of __block variable]
那么,__block类型变量是怎样支持改动的呢?
我们为int类型变量加上__block指示符,使得变量i能够在block函数体中被改动。
此时再看中间代码,会多出非常多信息。首先是__block变量相应的结构体:
由第一个成员__isa指针也能够知道__Block_byref_i_0也能够是NSObject。
第二个成员__forwarding指向自己,为什么要指向自己?指向自己是没有意义的,仅仅能说有时候须要指向还有一个__Block_byref_i_0结构。
最后一个成员是目标存储变量i。
此时,__main_block_impl_0结构例如以下:
__main_block_impl_0的成员变量i变成了__Block_byref_i_0 *类型。
相应的函数__main_block_func_0例如以下:
亮点是__Block_byref_i_0指针类型变量i,通过其成员变量__forwarding指针来操作还有一个成员变量。 :-)
而main函数例如以下:
通过这样看起来有点复杂的改变,我们能够改动变量i的值。可是问题相同存在:__Block_byref_i_0类型变量i仍然处于栈上,当block被回调运行时,变量i所在的栈已经被展开,怎么办?
在这样的关键时刻,__main_block_desc_0站出来了:
此时,__main_block_desc_0多了两个成员函数:copy和dispose,分别指向__main_block_copy_0和__main_block_dispose_0。
当block从栈上被copy到堆上时,会调用__main_block_copy_0将__block类型的成员变量i从栈上拷贝到堆上;而当block被释放时,对应地会调用__main_block_dispose_0来释放__block类型的成员变量i。
一会在栈上,一会在堆上,那假设栈上和堆上同一时候对该变量进行操作,怎么办?
这时候,__forwarding的作用就体现出来了:当一个__block变量从栈上被拷贝到堆上时,栈上的那个__Block_byref_i_0结构体中的__forwarding指针也会指向堆上的结构。
/* ---------------------------------------------------------------------------------------------------- */
本来还想继续写下去,结果发现文章有点长了。先到此。
原文链接:http://blog.csdn.net/jasonblog/article/details/7756763
Jason Lee @ Hangzhou
iOS中block实现的探究的更多相关文章
- iOS中block的用法 以及和函数用法的区别
ios中block的用法和函数的用法大致相同 但是block的用法的灵活性更高: 不带参数的block: void ^(MyBlock)() = ^{}; 调用的时候 MyBlock(); 带参数的 ...
- iOS 中Block以及Blocks的使用,闭包方法调用
OC: -(void)dataWithUrl:(NSString*)string AndId:(NSInteger)id returnName:(void(^)(NSString*name))back ...
- iOS中block类型大全
iOS中block类型大全 typedef的block 作为属性的block 作为变量的block 作为方法变量入参的block 作为方法参数的block 无名block 内联函数的block 递归调 ...
- iOS中Block介绍(一)基础
ios开发block的使用指南,以及深入理解block的内存管理,也适用于osx开发.讨论范围:block的使用,内存管理,内部实现.不包含的内容:gc arc下的block内存,block在c++中 ...
- iOS中Block介绍 基础
ios开发block的使用指南,以及深入理解block的内存管理,也适用于osx开发.讨论范围:block的使用,内存管理,内部实现.不包含的内容:gc arc下的block内存,block在c++中 ...
- ios中block中的探究
http://blog.csdn.net/jasonblog/article/details/7756763
- IOS中block和代理
从ios4开始引入block,就是代码块,结构类c语言 基本结构 返回值 (^block名称)(参数):int(^BlockName)(int):返回值为int型,参数是一个int值的叫BlockNa ...
- iOS中Block使用探索
Block介绍 Block在ios 4.0之后加入,并大量使用在新的ios api中.block是一个匿名的代码块,可以传递给其他对象的参数,并得到返回值.从本质上讲,block同其他普通的变量类似, ...
- iOS中Block的用法,举例,解析与底层原理(这可能是最详细的Block解析)
1. 前言 Block:带有自动变量(局部变量)的匿名函数.它是C语言的扩充功能.之所以是拓展,是因为C语言不允许存在这样匿名函数. 1.1 匿名函数 匿名函数是指不带函数名称函数.C语言中,函数是怎 ...
随机推荐
- OD: Ring0 & Kernel
开发技术讲究封装与模块化,安全技术强调底层安全性.安全技术需要打开封装.追根溯源! <0day 安全:软件漏洞分析技术(第2版)> 第21章 探索 Ring0 笔记 Intel x86 系 ...
- JAVA File转Byte[]
/** * 获得指定文件的byte数组 */ public static byte[] getBytes(String filePath){ byte[] buffer = null; try { F ...
- Session深度探索
什么是Session? web是无状态,这意味着每次页面被回传到服务器时,都重新生成一个web页面类的一个新的实例.众所周知http时无状态的协议.它不能获得客户端的信息.如果用户录入了一些信息,当跳 ...
- ASP.net关于C#代码与javaScript函数的相互调用
C#代码与javaScript函数的相互调用 问:1.如何在JavaScript访问C#函数?2.如何在JavaScript访问C#变量?3.如何在C#中访问JavaScript的已有变量?4.如何在 ...
- 请阐述调用Activity有哪几种方法,并写出相关的Java代码
请阐述调用Activity有哪几种方法,并写出相关的Java代码. 答案:可以采用两种方式调用Activity:显示调用和隐式调用.显示调用直接指定了Activity,代码如下: Intent int ...
- jQuery 小知识点(插件)
1.jQuery插件小知识点: 估计很多人都没弄明白下面的东西,特从网络上搜索了下面的知识,自己以后用起来也比较方便: $.fn是指jquery的命名空间,加上fn上的方法及属性,会对jquery实例 ...
- Windows Server 2012从Evaluation版转成正式版
步骤 运行->CMD(管理员)->输入DISM /online /Get-CurrentEdition 看你的Edition ID是什么,如果是Evaluation的话,例如Standar ...
- javascript 网页图标音乐切换
图片名称 sprite.zip <!doctype html> <html> <head> </head> <style> .css{ po ...
- shell脚本实现仅保留某目录下最新的两个文件
#!/bin/sh export DS_DIR=/home/cxy/test if [ ! -d $DS_DIR ]; then mkdir $DS_DIR else echo "$DS_D ...
- 机器学习之python: kNN
################################################## # kNN : k Nearest Neighbour # Author : Monne # Da ...