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

时间: 2024-11-03 22:36:05

iOS中block实现的探究的相关文章

iOS中block类型大全

iOS中block类型大全 typedef的block 作为属性的block 作为变量的block 作为方法变量入参的block 作为方法参数的block 无名block 内联函数的block 递归调用的block 作为方法返回值的block 作为函数名的block(太过奇葩,完全不知道怎么用-_-!) iOS中block类型大全,码迷,mamicode.com

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

ios中block的用法和函数的用法大致相同 但是block的用法的灵活性更高: 不带参数的block: void ^(MyBlock)() = ^{}; 调用的时候  MyBlock(); 带参数的block: int ^(MyBlock)(int,int) = ^(int a,int b){return a+b;} 调用MyBlock(5,6); 将block当作某个类的属性的写法 typedef void (^BlockOption)(); @property (nonatomic,ass

IOS中Block的循环引用

@interface DemoObj() @property (nonatomic, strong) NSOperationQueue *queue; @end @implementation DemoObj - (instancetype)init { self = [super init]; if (self) { self.queue = [[NSOperationQueue alloc] init]; } return self; } - (void)dealloc { NSLog(@"

iOS 中Block以及Blocks的使用

一.ios中block的使用 Block可以帮助我们组织独立的代码段,并提高复用性和可读性.iOS4在UIKit中引入了该特征.超过100个的Apple API都使用了Block,所以这是一个我们必须开始熟悉的知识. Block是什么样的? 你可以使用^操作符来声明一个Block变量,它表示一个Block的开始. int num1 = 7; int(^aBlock)(int) = ^(int num2) { return num1+nunm2; }; 在如上代码中我们将Block声明为一个变量,

iOS中block用法之两个界面传值问题

Block的使用有很多方面,其中传值只是它的一小部分,但是很常用更实用,下面介绍Block在两个界面之间的传值用法: 先说一下思想: 首先,创建两个视图控制器,在第一个视图控制器中创建一个Label和一个Button,其中Label是为了显示第二个视图控制器传过来的字符串, Button是为了push到第二个界面. 第二个界面的只创建一个TextField,是为了输入文字,当输入文字并且返回第一个界面的时候(第二个视图将要消失的时候),就将这个 TextFiled中的文字传给第一个界面并且显示在

IOS中block的使用方法

X.1 初探Block 在这一小节我们先用一些简单范例来导入block的概念. X.1.1 宣告和使用Block 我们使用「^」运算子来宣告一个block变数,而且在block的定义最后面要加上「;」来表示一个完整的述句(也就是将整个block定义视为前面章节所介绍的简单述句,因为整个定义必须是一个完整的句子,所以必须在最后面加上分号),下面是一个block的范例: 1: int multiplier = 7 ; 2: int (^myBlock)( int ) = ^( int num) 3:

IOS中 Block简介与用法(一)

?Block简介: Block的实际行为和Function很像,最大的差别是在可以存取同一个Scope的变量值.Block实体形式如下: ^(传入参数列){行为主体}; Block实体开头是"^",接着是由小括号所包起来的参数列(比如 int a, int b, int c),行为主体由大括号包起来,专有名字叫做block literal.行为主体可以用return回传值,类型会被compiler自动辨别.如果没有参数列要写成: ^(void). 例如下面的一个例子: [cpp] vi

iOS中Block介绍(一)基础

ios开发block的使用指南,以及深入理解block的内存管理,也适用于osx开发.讨论范围:block的使用,内存管理,内部实现.不包含的内容:gc arc下的block内存,block在c++中的使用. AD: 一.概述 Block是C级别的语法和运行时特性.Block比较类似C函数,但是Block比之C函数,其灵活性体现在栈内存.堆内存的引用,我们甚至可以将一个Block作为参数传给其他的函数或者Block. 二.热身 先看一个比较简单的Block例子: int multiplier =

iOS中Block使用探索

Block介绍 Block在ios 4.0之后加入,并大量使用在新的ios api中.block是一个匿名的代码块,可以作为传递给其他对象的参数,并得到返回值.从本质上讲,block同其他普通的变量类似,只是其储存的数据是一个函数体.Block不只是针对Objective-C的专利,而是一种可以应用于C.C++和OBjective-C的语言层面的新特性.通过使用block,开发者可以将一段代码段像某一个数值一样当做参数传递给函数.同时,blocks也是Objective-C的一种对象,可以像其他