zoukankan      html  css  js  c++  java
  • iOS

    BlockiOS开发中一种比较特殊的数据结构,它可以保存一段代码,在合适的地方再调用,具有语法简介、回调方便、编程思路清晰、执行效率高等优点,受到众多猿猿的喜爱。但是Block在使用过程中,如果对Block理解不深刻,容易出现Cycle Retain的问题。本文主要从ARC模式下解析一下Block的底层实现,以及Block的三种类型(栈、堆、全局)的区别。

    一、Block定义

    1. Block 定义及使用

    返回值类型 (^block变量名)(形参列表) = ^返回值类型 (形参列表) {
    };
    
    // 调用Block保存的代码
    block变量名(实参);
    

    2. 项目中使用格式

    在项目中,通常会重新定义block的类型的别名,然后用别名来定义block的类型

    // 定义block类型
    typedef void (^Block)(int);
    
    // 定义block
    Block block = ^(int a){};
    
    // 调用block
    block(3);
    

    二、Block底层实现

    block的底层实现是结构体,和类的底层实现类似,都有isa指针,可以把block当成是一个对象。下面通过创建一个控制台程序,来窥探block的底层实现

    1. block内存结构

    block 的内存结构图
    block内存结构图

    Block_layout结构体成员含义如下:

    • isa: 指向所属类的指针,也就是block的类型
    • flags: 标志变量,在实现block的内部操作时会用到
    • Reserved: 保留变量
    • invoke: block执行时调用的函数指针,block内部的执行代码都在这个函数中
    • descriptor: block的详细描述,包含 copy/dispose 函数,处理block引用外部变量时使用
    • variables: block范围外的变量,如果block没有调用任何外部变量,该变量就不存在

    Block_descriptor结构体成员含义如下:

    • reserved: 保留变量
    • size: block的内存大小
    • copy: 拷贝block中被 __block 修饰的外部变量
    • dispose: 和 copy 方法配置应用,用来释放资源

    具体实现代码如下(代码来自Block_private.h):

    enum {
        BLOCK_REFCOUNT_MASK =     (0xffff),
        BLOCK_NEEDS_FREE =        (1 << 24),
        BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
        BLOCK_HAS_CTOR =          (1 << 26), /* Helpers have C++ code. */
        BLOCK_IS_GC =             (1 << 27),
        BLOCK_IS_GLOBAL =         (1 << 28),
        BLOCK_HAS_DESCRIPTOR =    (1 << 29)
    };
    
    /* Revised new layout. */
    struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *);
    };
    
    struct Block_layout {
        void *isa;
        int flags;
        int reserved; 
        void (*invoke)(void *, ...);
        struct Block_descriptor *descriptor;
        /* Imported variables. */
    };
    

    2. 创建block

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        
    		// 最简block
            ^{ };
        }
        return 0;
    }
    

    3. 转换结构

    利用 clang*.m 的文件转换为 *.cpp 文件,就可以看到 block 的底层实现了

    $ clang -rewrite-objc main.m 
    

    转换后的代码:

    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    }
    
    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)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
        }
        return 0;
    }
    

    从代码中可以看出,__main_block_impl_0就是blockC++实现(最后面的_0代表是main中的第几个block),__main_block_func_0block的代码块,__main_block_desc_0block的描述,__block_implblock的定义。

    __block_impl成员含义如下:

    • isa: 指向所属类的指针,也就是block的类型
    • flags,标志变量,在实现block的内部操作时会用到
    • Reserved,保留变量
    • FuncPtr,block执行时调用的函数指针

    __main_block_impl_0解释如下:

    • impl: block对象
    • Desc: block对象的描述

    其中,__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) 这是显式构造函数,flags的默认值为0,函数体如下:

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    

    可以看出:

    • __main_block_impl_0的isa指针指向了_NSConcreteStackBlock,所有的局部block都是在栈上门创建
    • 从main函数中看, __main_block_impl_0的FuncPtr指向了函数__main_block_func_0
    • __main_block_impl_0的Desc也指向了定义__main_block_desc_0时就创建的__main_block_desc_0_DATA,其中Block_size记录了block结构体大小等信息

    __main_block_desc_0成员含义如下:

    • reserved: 保留变量
    • Block_size: block内存大小,sizeof(struct __main_block_impl_0)

    三、Block类型

    block有三种类型:

    • _NSConcreteGlobalBlock: 存储在全局数据区
    • _NSConcreteStackBlock: 存储在栈区
    • _NSConcreteMallocBlock: 存储在堆区

    APUE的进程虚拟内存段分布图如下:

    内存分布图

    其中,_NSConcreteGlobalBlock_NSConcreteStackBlock 可以由程序创建,而 _NSConcreteMallocBlock 则无法由程序创建,只能由 _NSConcreteStackBlock 通过拷贝生成。

    1. 全局block 和 栈block

    测试代码如下:

    void (^globalBlock)() = ^{
    
    };
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        
            void (^stackBlock1)() = ^{
    
            };
        }
        return 0;
    }
    

    clang转换后的代码如下:

    // globalBlock
    struct __globalBlock_block_impl_0 {
      ...
      __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteGlobalBlock;
        ...
      }
    };
    ...
    
    // stackBlock
    struct __main_block_impl_0 {
      ...
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        ...
      }
    };
    ...
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
        }
        return 0;
    }
    

    可以看出, globalBlockisa 指向了 _NSConcreteGlobalBlock,即在全局区域创建,编译时其具体代码在代码段中,block变量则存储在全局数据区;而stackBlockisa 则指向了 _NSConcreteStackBlock,表明在栈区创建。

    2. 捕获外部参数对栈区block结构的影响

    由于堆区 block 是由栈区 block 转化而成, 所以下面主要分析栈区 block 如何转化为堆区 block

    捕获局部非静态变量

    代码:

        int a = 0;
        ^{a;};
    

    转化后:

    struct __Person__test_block_impl_0 {
      ...
      int a;
      // a(_a)是构造函数的参数列表初始化形式,相当于a = _a。从_I_Person_test看,传入的就是a
      __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        ...
      }
    };
    
    static void _I_Person_test(Person * self, SEL _cmd) {
        int a;
        (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, a);
    }
    

    可以看到,block 对于栈区变量的引用只是值传递,由于 block 内部变量 a 和 外部变量 a 不在同一个作用域,所以在 block 内部不能把变量 a 作为左值(left-value),因为赋值没有意义。所以,如果出现如下代码,编译器会提示错误:

    a = 10;
    

    捕获局部静态变量

    代码:

    	static int a;
        ^{
            a = 10;
        };
    

    转换后:

    struct __Person__test_block_impl_0 {
      ...
      int *a;
      __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        ...
      }
    };
    static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
      int *a = __cself->a; // bound by copy
      // 这里通过局部静态变量a的地址来对其进行修改
      (*a) = 10;
    }
    
    static void _I_Person_test(Person * self, SEL _cmd) {
        static int a;
        // 传入a的地址
        (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, &a);
    }
    

    由于局部静态变量也存储在静态数据区,和程序拥有一样的生命周期,但是其作用范围局限在定义它的函数中,所有在block里是通过地址来访问。

    捕获全局变量

    代码:

    // 全局静态
    static int a;
    // 全局
    int b;
    - (void)test
    {
    
        ^{
            a = 10;
            b = 10;
        };
    }
    

    转换后:

    static int a;
    int b;
    
    static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
    
      a = 10;
      b = 10;
    }
    
    static void _I_Person_test(Person * self, SEL _cmd) {
    
        (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA);
    }
    

    可以看出,因为全局变量都在静态数据区,在程序结束前不会被销毁,所以block直接访问了对应的变量,而没有在Persontest_block_impl_0结构体中给变量预留位置。

    捕获对象的变量

    代码:

    @interface Person()
    {
        int _a;
    }
    
    @end
    
    @implementation Person
    
    - (void)test
    {
        void (^block)() = ^{
            _a;
        };
    }
    
    @end
    

    转换后:

    struct __Person__test_block_impl_0 {
      ...
      Person *self;
      __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        ...
      }
    };
    
    static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
      Person *self = __cself->self; // bound by copy
    
      (*(int *)((char *)self + OBJC_IVAR_$_Person$_a));
    }
    
    static void _I_Person_test(Person * self, SEL _cmd) {
        void (*block)() = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
    }
    

    可以看到,即使 block只是引用对象的变量,但是底层依然引用的是对象本身 self,这和直接使用 self.a产生的循环引用的问题是一样的。所以,要在 block 内使用对象的弱引用,即可解决循环引用的问题。并且,对变量a的访问也是通过 self的地址加 a的偏移量的形式。

    捕获__block修饰的基本变量

    代码:

    	__block int a;
        ^{
            a = 10;
        };
    

    转换后:

    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    
    struct __main_block_impl_0 {
      ...
      __Block_byref_a_0 *a; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        ...
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_a_0 *a = __cself->a; // bound by ref
      (a->__forwarding->a) = 10;
    }
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __main_block_desc_0 {
      ...
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
    
            ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        }
        return 0;
    }
    

    可以看到,被__block修饰的变量被封装成了一个对象,类型为__Block_byref_a_0,然后把&a作为参数传给了block

    __Block_byref_a_0 成员含义如下:

    • __isa: 指向所属类的指针,被初始化为 (void*)0
    • __forwarding: 指向对象在堆中的拷贝
    • __flags: 标志变量,在实现block的内部操作时会用到
    • __size: 对象的内存大小
    • a: 原始类型的变量

    其中,isa__flags__size 的含义和之前类似,而 __forwarding 是用来指向对象在堆中的拷贝,runtime.c 里有源码说明:

    static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
        ...
        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
        // 堆中拷贝的forwarding指向它自己
        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
        // 栈中的forwarding指向堆中的拷贝
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        ...
    }
    

    这样做是为了保证在 block内 或 block 变量后面对变量a的访问,都是直接访问堆内的对象,而不上栈上的变量。同时,在 block 拷贝到堆内时,它所捕获的由 __block 修饰的局部基本类型也会被拷贝到堆内(拷贝的是封装后的对象),从而会有 copydispose处理函数。

    Block_byref的结构定义在 Block_private.h 文件里有介绍:

    struct Block_byref {
        void *isa;
        struct Block_byref *forwarding;
        int flags; /* refcount; */
        int size;
        void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
        void (*byref_destroy)(struct Block_byref *);
        /* long shared[0]; */
    };
    
    // flags/_flags类型
    enum {
            /* See function implementation for a more complete description of these fields and combinations */
            // 是一个对象
            BLOCK_FIELD_IS_OBJECT   =  3,  /* id, NSObject, __attribute__((NSObject)), block, ... */
            // 是一个block
            BLOCK_FIELD_IS_BLOCK    =  7,  /* a block variable */
            // 被__block修饰的变量
            BLOCK_FIELD_IS_BYREF    =  8,  /* the on stack structure holding the __block variable */
            // 被__weak修饰的变量,只能被辅助copy函数使用
            BLOCK_FIELD_IS_WEAK     = 16,  /* declared __weak, only used in byref copy helpers */
            // block辅助函数调用(告诉内部实现不要进行retain或者copy)
            BLOCK_BYREF_CALLER      = 128  /* called from __block (byref) copy/dispose support routines. */
    };
    

    可以看到,Block_byref__Block_byref_a_0 的前4个成员类型相同,可以互相转化。

    ** copy 函数

    copy 的实现函数是 _Block_object_assign,它根据对象的 flags 来判断是否需要拷贝,或者只是赋值,函数实现在 runtime.c 里:

    // _Block_object_assign源码
    void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    ...
        else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
            // copying a __block reference from the stack Block to the heap
            // flags will indicate if it holds a __weak reference and needs a special isa
            _Block_byref_assign_copy(destAddr, object, flags);
        }
    ...
    }
    
    // _Block_byref_assign_copy源码
    static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
        // 这里因为前面4个成员的内存分布一样,所以直接转换后,使用Block_byref的成员变量名,能访问到__Block_byref_a_0的前面4个成员
        struct Block_byref **destp = (struct Block_byref **)dest;
        struct Block_byref *src = (struct Block_byref *)arg;
    ...
        else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            // 从main函数对__Block_byref_a_0的初始化,可以看到初始化时将flags赋值为0
            // 这里表示第一次拷贝,会进行复制操作,并修改原来flags的值
            // static int _Byref_flag_initial_value = BLOCK_NEEDS_FREE | 2;
            // 可以看出,复制后,会并入BLOCK_NEEDS_FREE,后面的2是block的初始引用计数
            ...
            copy->flags = src->flags | _Byref_flag_initial_value;
            ...
        }
        // 已经拷贝到堆了,只增加引用计数
        else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
            latching_incr_int(&src->forwarding->flags);
        }
        // 普通的赋值,里面最底层就*destptr = value;这句表达式
        _Block_assign(src->forwarding, (void **)destp);
    }
    

    主要操作都在代码注释中了,总体来说,__block修饰的基本类型会被包装为对象,并且只在最初block拷贝时复制一次,后面的拷贝只会增加这个捕获变量的引用计数。

    ** dispose 函数

    dispose 的实现函数是 _Block_object_dispose,代码依然可以在 runtime.c 里:

    void _Block_object_dispose(const void *object, const int flags) {
        //printf("_Block_object_dispose(%p, %x)
    ", object, flags);
        if (flags & BLOCK_FIELD_IS_BYREF)  {
            // get rid of the __block data structure held in a Block
            _Block_byref_release(object);
        }
        ...
    }
    
    // Old compiler SPI
    static void _Block_byref_release(const void *arg) {
        struct Block_byref *shared_struct = (struct Block_byref *)arg;
        int refcount;
    
        // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
        shared_struct = shared_struct->forwarding;
        
        ...
        refcount = shared_struct->flags & BLOCK_REFCOUNT_MASK;
        ...
        else if ((latching_decr_int(&shared_struct->flags) & BLOCK_REFCOUNT_MASK) == 0) {
            ...
        }
    }
    

    可以看到,被__block修改的变量,释放时要 latching_decr_int减引用计数,直到计数为0,就释放改对象;而普通的对象、block,就直接释放销毁。

    捕获没有__block修饰的对象

    代码:

    	NSObject *a = [[NSObject alloc] init];
        Block block = ^ {
            a;
        };
    

    转换后部分结果如下:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      NSObject *a = __cself->a; // bound by copy
      a;
    }
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            ...
            Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, 570425344));
        }
        return 0;
    }
    

    对象在没有__block修饰时,并没有产生__Block_byref_a_0结构体,只是将标志位修改为BLOCK_FIELD_IS_OBJECT。而在_Block_object_assign中对应的判断分支代码如下:

    ...
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        _Block_retain_object(object);
        _Block_assign((void *)object, destAddr);
    }
    ...
    

    可以看到,block复制时,会retain捕捉对象,以增加其引用计数。

    捕获有__block修饰的对象

    代码:

        __block NSObject *a = [[NSObject alloc] init];
        Block block = ^ {
            a;
        };
    

    转化后部分结果如下:

    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSObject *a;
    };
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
    attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131,....};
    Block block = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
    }
    
    // 以下的40表示__Block_byref_a_0对象a的位移(4个指针(32字节)+2个int变量(8字节)=40字节)
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    

    可以看到,对于对象,处理增加了__Block_byref_a_0外,还另外增加了两个辅助函数__Block_byref_id_object_copy__Block_byref_id_object_dispose,以实现对对象内存的管理。其中两者的最后一个参数131表示BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECTBLOCK_BYREF_CALLER表示在内部实现中不对a对象进行retaincopy;以下为_Block_object_assign函数的部分代码:

    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        ...
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    

    _Block_byref_assign_copy函数的以下代码会对上面的辅助函数__Block_byref_id_object_copy_131进行调用;570425344表示BLOCK_HAS_COPY_DISPOSE | BLOCK_HAS_DESCRIPTOR,即(1<<25 | 1<<29),_Block_byref_assign_copy函数的部分代码:

    if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
        // Trust copy helper to copy everything of interest
        // If more than one field shows up in a byref block this is wrong XXX
        copy->byref_keep = src->byref_keep;
        copy->byref_destroy = src->byref_destroy;
        (*src->byref_keep)(copy, src);
    }
    

    四、ARC中block的工作特点

    ARC模式下,在栈间传递block时,不需要手动copy栈中的block,即可让block正常工作。主要原因是ARC对栈中的block自动执行了copy,将_NSConcreteStackBlock类型的block转换成了_NSConcreteMallocBlock类型的block

    1. block,非函数参数

    代码:

    	int i = 10;
        void (^block)() = ^{i;};
        
        __unsafe_unretained void (^weakBlock)() = ^{i;};
        
        void (^stackBlock)() = ^{};
        
        NSLog(@"%@", ^{i;});
        
        NSLog(@"%@", block);
        
        NSLog(@"%@", weakBlock);
        
        NSLog(@"%@", stackBlock);
        
        /* ARC
         <__NSStackBlock__: 0x7fff5fbff708>
         <__NSMallocBlock__: 0x100300000>
         <__NSStackBlock__: 0x7fff5fbff738>
         <__NSGlobalBlock__: 0x100001110>
         */
        
        /* MRC
         <__NSStackBlock__: 0x7fff5fbff6e0>
         <__NSStackBlock__: 0x7fff5fbff740>
         <__NSStackBlock__: 0x7fff5fbff710>
         <__NSGlobalBlock__: 0x1000010e0>*/
    

    从打印结果可以看出,ARC模式下,block 只有引用了外部的变量,并且被强引用,才会被拷贝到堆上;只引用了外部的变量,或者被弱引用都只在栈上创建;如果没有引用外部变量,无论是否被强引用,都会被转换为全局 block,也就是说,在编译时,这个block的所有内容已经在代码段中生成了。

    2. block,作为参数传递

    代码

    typedef void (^Block)();
    
    NSMutableArray *arrays;
    
    void testBlock() {
        int a = 5;
        
        [arrays addObject:^{
            NSLog(@"%d", a);
        }];
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here..
            
            arrays = @[].mutableCopy;
            
            testBlock();
            
            Block block = [arrays firstObject];
            
            NSLog(@"%@", block);
            
            /* ARC
             * <__NSMallocBlock__: 0x1006034c0>
             */
            
            /* MRC
             * 崩溃,野指针
             */
        }
        return 0;
    }
    

    可以看出,ARC模式下,栈区的block 被拷贝到了堆区,在 testBlock 函数结束后依然可以访问;而 MRC模式下,由于我们没有手动执行[block copy]来将block拷贝到堆区,随着函数生命周期结束,block被销毁,访问时出现野指针错误,但是如果把testBlock函数中的block打印语句删掉:

    NSLog(@"%d", a);
    

    那么,block就变为全局的,在MRC模式下,再次访问不会出错。


    参考文章

    http://www.jianshu.com/p/51d04b7639f1

    http://www.jianshu.com/p/aff2cad778c0

    http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/

    runtime.c

    Block.private.h

  • 相关阅读:
    JSP页面
    JSP简介
    常量与变量的声明与使用
    AJAX无刷新上传图片
    JSP连接MySql数据库
    运算符与表达式
    世界级的javascript ajax client端UI库 Ext学习笔记 menu组件 和 toolbar组件
    OOD/OOA基本原则
    Java字符编码转换过程说明
    正则表达式系统教程
  • 原文地址:https://www.cnblogs.com/fishbay/p/7204278.html
Copyright © 2011-2022 走看看