[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示比例如以下:

  1. int (^maxBlock)(int, int) = ^(int x, int y) { return x > y ? x : y; };

假设用Python的lambda表达式来写,能够写成例如以下形式:

  1. f = lambda x, y : x if x > y else y

只是由于Python自身的语言特性,在def定义的函数体中,能够非常自然地再用def语句定义内嵌函数,由于这些函数本质上都是对象。

假设用BNF来表示block的上下文无关文法,大致例如以下:

  1. block_expression ::= ^ block_declare block_statement
  2. block_declare ::= block_return_type block_argument_list
  3. block_return_type ::= return_type |
  4. block_argument_list ::= argument_list |

[1. Why block]

Block除了可以定义參数列表、返回类型外,还可以获取被定义时的词法范围内的状态(比方局部变量),而且在一定条件下(比方使用__block变量)可以改动这些状态。此外,这些可改动的状态在同样词法范围内的多个block之间是共享的,即便出了该词法范围(比方栈展开,出了作用域),仍可以继续共享或者改动这些状态。

通常来说,block都是一些简短代码片段的封装,适用作工作单元,通经常使用来做并发任务、遍历、以及回调。

比方我们能够在遍历NSArray时做一些事情:

  1. - (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实现的探究的更多相关文章

  1. iOS中block的用法 以及和函数用法的区别

    ios中block的用法和函数的用法大致相同 但是block的用法的灵活性更高: 不带参数的block: void ^(MyBlock)() = ^{}; 调用的时候  MyBlock(); 带参数的 ...

  2. iOS 中Block以及Blocks的使用,闭包方法调用

    OC: -(void)dataWithUrl:(NSString*)string AndId:(NSInteger)id returnName:(void(^)(NSString*name))back ...

  3. iOS中block类型大全

    iOS中block类型大全 typedef的block 作为属性的block 作为变量的block 作为方法变量入参的block 作为方法参数的block 无名block 内联函数的block 递归调 ...

  4. iOS中Block介绍(一)基础

    ios开发block的使用指南,以及深入理解block的内存管理,也适用于osx开发.讨论范围:block的使用,内存管理,内部实现.不包含的内容:gc arc下的block内存,block在c++中 ...

  5. iOS中Block介绍 基础

    ios开发block的使用指南,以及深入理解block的内存管理,也适用于osx开发.讨论范围:block的使用,内存管理,内部实现.不包含的内容:gc arc下的block内存,block在c++中 ...

  6. ios中block中的探究

    http://blog.csdn.net/jasonblog/article/details/7756763

  7. IOS中block和代理

    从ios4开始引入block,就是代码块,结构类c语言 基本结构 返回值 (^block名称)(参数):int(^BlockName)(int):返回值为int型,参数是一个int值的叫BlockNa ...

  8. iOS中Block使用探索

    Block介绍 Block在ios 4.0之后加入,并大量使用在新的ios api中.block是一个匿名的代码块,可以传递给其他对象的参数,并得到返回值.从本质上讲,block同其他普通的变量类似, ...

  9. iOS中Block的用法,举例,解析与底层原理(这可能是最详细的Block解析)

    1. 前言 Block:带有自动变量(局部变量)的匿名函数.它是C语言的扩充功能.之所以是拓展,是因为C语言不允许存在这样匿名函数. 1.1 匿名函数 匿名函数是指不带函数名称函数.C语言中,函数是怎 ...

随机推荐

  1. OD: Ring0 & Kernel

    开发技术讲究封装与模块化,安全技术强调底层安全性.安全技术需要打开封装.追根溯源! <0day 安全:软件漏洞分析技术(第2版)> 第21章 探索 Ring0 笔记 Intel x86 系 ...

  2. JAVA File转Byte[]

    /** * 获得指定文件的byte数组 */ public static byte[] getBytes(String filePath){ byte[] buffer = null; try { F ...

  3. Session深度探索

    什么是Session? web是无状态,这意味着每次页面被回传到服务器时,都重新生成一个web页面类的一个新的实例.众所周知http时无状态的协议.它不能获得客户端的信息.如果用户录入了一些信息,当跳 ...

  4. ASP.net关于C#代码与javaScript函数的相互调用

    C#代码与javaScript函数的相互调用 问:1.如何在JavaScript访问C#函数?2.如何在JavaScript访问C#变量?3.如何在C#中访问JavaScript的已有变量?4.如何在 ...

  5. 请阐述调用Activity有哪几种方法,并写出相关的Java代码

    请阐述调用Activity有哪几种方法,并写出相关的Java代码. 答案:可以采用两种方式调用Activity:显示调用和隐式调用.显示调用直接指定了Activity,代码如下: Intent int ...

  6. jQuery 小知识点(插件)

    1.jQuery插件小知识点: 估计很多人都没弄明白下面的东西,特从网络上搜索了下面的知识,自己以后用起来也比较方便: $.fn是指jquery的命名空间,加上fn上的方法及属性,会对jquery实例 ...

  7. Windows Server 2012从Evaluation版转成正式版

    步骤 运行->CMD(管理员)->输入DISM /online /Get-CurrentEdition 看你的Edition ID是什么,如果是Evaluation的话,例如Standar ...

  8. javascript 网页图标音乐切换

    图片名称 sprite.zip <!doctype html> <html> <head> </head> <style> .css{ po ...

  9. shell脚本实现仅保留某目录下最新的两个文件

    #!/bin/sh export DS_DIR=/home/cxy/test if [ ! -d $DS_DIR ]; then mkdir $DS_DIR else echo "$DS_D ...

  10. 机器学习之python: kNN

    ################################################## # kNN : k Nearest Neighbour # Author : Monne # Da ...