zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    java发送http的get、post请求
    spring boot注解 --@EnableAsync 异步调用
    java代码将e.printStackTrace()写入log4j文件异常信息
    Mybatis 传入List类型参数,报错:There is no getter for property named '__frch_item_0' in
    mongodb 只查询一个字段
    如何在java List中进行模糊查询
    java操作Mongodb
    java操作mongodb时,对象bean和DBObject相互转换的方法
    java 字符串,字符数组,list间的转化
    ldconfig 让安装的 php 的rdkafka生效
  • 原文地址:https://www.cnblogs.com/mindyme/p/4650884.html
Copyright © 2011-2022 走看看