• OC中的block


    block是我在项目中比较常用,也比较爱用的一项技术。原因有两点:

    • 使代码更紧凑,可读性更强
    • 可capture上下文中的变量

    当然,block使用不恰当的话,也会引起一些难以发现和追踪的问题:

    • 循环引用,以及
    • 会延长其capture的上下文中的变量的生命周期。

    至于其他的,比如代码紧凑带来的代码增长,这些就见仁见智了。

    一直以为针对block的使用和理解没什么问题,最近在看一篇技术博文时发现仍有些细节不太清楚,趁此机会学习和研究了block在编译器内部的实现方式,便有了此文。

    block的内部实现数据结构

    本质上block就是一段代码,再加上它所访问的上下文中的变量。clang中block的结构是类似于这样的:

    struct \_\_main\_block\_impl\_0 { 
        struct \_\_block_impl impl; 
        struct \_\_main\_block\_desc\_0* Desc; 
        int a; 
        \_\_main_block_impl_0(void *fp, struct \_\_main\_block\_desc\_0 *desc, int \_a, int flags=0) : a(\_a) { 
            impl.isa = &_NSConcreteStackBlock; 
            impl.Flags = flags; 
            impl.FuncPtr = fp; 
            Desc = desc; 
        } 
    }; 
    

    其中,impl代表的是block的实现。具体结构如下:

    struct __block\_impl { 
        void *isa; 
        int Flags; 
        int Reserved; 
        void *FuncPtr; 
    }; 
    

    Desc是block的描述。具体结构如下:

    static struct __main_block_desc_0 { 
        size_t reserved; 
        size_t Block_size; 
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) }
    

    a是block capture的上下文变量。
    _main_block_impl_0()函数具体显示了block被创建时是如何capture 变量的。本例中为按值拷贝到_main_block_impl_0结构体中的那个变量a. 因此,在block内部修改是不能修改外部a的值的。

    block如何存储

    block分成三种类型:

    • NSGlobalBlock, 既不在stack上也不在heap上,而是代码片段。 没有capture上下文变量的block为此类型
    • NSStackBlock, 存储在stack上。 capture上下文变量的block默认为次类型。当对NSStackBlock发生Block_Copy时,拷贝将为NSMallocBlock类型。
    • NSMallocBlock, 存储在heap上

    顺便说下什么是stack和heap。可以这么理解,stack是内存中用于存储local variables的区域;每个线程有自己的一个stack。每当有函数调用时,就在调用线程的stack上分配一个栈帧,用来存储函数的局部数据;函数调用结束时弹栈。 heap则可以简单理解为内存中其他数据的存储区域。oc对象都是存放在heap上的(block除外,可以存放在stack上)。详见mikeash的博客
    关于block的存储,有一个有意思的测试

    block如何capture上下文中的变量

    当capture的上下文变量用__block来修饰时, 表示它是可以被block修改的。从clang编译后的结果可以看出,block是复制变量的引用地址来使用的。对于未用block修饰的变量,block是复制变量的值来使用的(复制的是block被创建时变量的值)

    另外,当block被拷贝时,它会retain所capture的对象。当用__block来修饰对象时(OC对象或者OC++对象),block将不再retain该对象(仅针对于MRC, 对于ARC不管用; ARC下应该使用__weak修饰符)

    __block变量被保存到block storage中,可以被变量所在的lexical scope, 以及该scope内创建的所有block共享。block storage可能在stack上,也可能在heap上。

    使用block需要注意的点

    • arc下不存在NSStackBlock类型的block, 而是NSMallocBlock类型。这大概是因为arc下的变量默认为strong的。arc下使用block会简单很多。这个测试很可以说明这个问题
    • 避免retain cycle。 MRC下可以使用__block来避免block对其capture的object variable进行retain操作; arc下可以使用__weak来避免

    本文的参考:

    1. http://blog.devtang.com/blog/2013/07/28/a-look-inside-blocks/
    2. https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html#//apple_ref/doc/uid/TP40007502-CH1-SW1
    3. http://stackoverflow.com/questions/7853915/how-do-i-avoid-capturing-self-in-blocks-when-implementing-an-api?rq=1
  • 相关阅读:
    数据结构之链表——加里森的任务(循环链表)
    数据结构之队列——回文字判断
    数据结构之栈——二进制转十进制
    《爱的艺术》人类超越了本能
    从一个Activity返回上一个Activity
    VS(C++)编程遇到的错误集合
    C++(MFC)编程一些注意事项
    Tomcat部署(进行web服务器开发)
    本地IP与宽带IP
    opencv的Mat图像显示在MFC控件中
  • 原文地址:https://www.cnblogs.com/mindyme/p/4650884.html
Copyright © 2020-2023  润新知