zoukankan      html  css  js  c++  java
  • 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


  • 相关阅读:
    python语法基础-并发编程-协程-长期维护
    python语法基础-并发编程-线程-其他
    python语法基础-并发编程-线程-线程池
    python语法基础-并发编程-线程-各种锁以及队列
    python语法基础-并发编程-线程-线程理论和线程的启动
    python语法基础-并发编程-进程-其他
    python语法基础-并发编程-进程-进程池以及回调函数
    python语法基础-并发编程-进程-进程锁和进程间通信
    python语法基础-并发编程-进程-进程理论和进程的开启
    二、提高期(Upping the Ante)
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/4008182.html
Copyright © 2011-2022 走看看