深入研究Block捕获外部变量和__block实现原理

作者: 一缕殇流化隐半边冰霜 | 来源:发表于2016-08-27 22:05 被阅读25415次

    前言

    Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这个新功能“Blocks”。从那开始,Block就出现在iOS和Mac系统各个API中,并被大家广泛使用。一句话来形容Blocks,带有自动变量(局部变量)的匿名函数。

    Block在OC中的实现如下:

    
    struct Block_layout {
        void *isa;
        int flags;
        int reserved;
        void (*invoke)(void *, ...);
        struct Block_descriptor *descriptor;
        /* Imported variables. */
    };
    
    struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *);
    };
    

    从结构图中很容易看到isa,所以OC处理Block是按照对象来处理的。在iOS中,isa常见的就是_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock这3种(另外只在GC环境下还有3种使用的_NSConcreteFinalizingBlock,_NSConcreteAutoBlock,_NSConcreteWeakBlockVariable,本文暂不谈论这3种,有兴趣的看看官方文档)

    以上介绍是Block的简要实现,接下来我们来仔细研究一下Block的捕获外部变量的特性以及__block的实现原理。

    研究工具:clang
    为了研究编译器的实现原理,我们需要使用 clang 命令。clang 命令可以将 Objetive-C 的源码改写成 C / C++ 语言的,借此可以研究 block 中各个特性的源码实现方式。该命令是

    clang -rewrite-objc block.c
    
    

    目录

    • 1.Block捕获外部变量实质
    • 2.Block的copy和release
    • 3.Block中__block实现原理

    一.Block捕获外部变量实质

    拿起我们的Block一起来捕捉外部变量吧。

    说到外部变量,我们要先说一下C语言中变量有哪几种。一般可以分为一下5种:

    • 自动变量
    • 函数参数
    • 静态变量
    • 静态全局变量
    • 全局变量

    研究Block的捕获外部变量就要除去函数参数这一项,下面一一根据这4种变量类型的捕获情况进行分析。

    我们先根据这4种类型

    • 自动变量
    • 静态变量
    • 静态全局变量
    • 全局变量

    写出Block测试代码。

    这里很快就出现了一个错误,提示说自动变量没有加__block,由于__block有点复杂,我们先实验静态变量,静态全局变量,全局变量这3类。测试代码如下:

    
    #import <Foundation/Foundation.h>
    
    int global_i = 1;
    
    static int static_global_j = 2;
    
    int main(int argc, const char * argv[]) {
       
        static int static_k = 3;
        int val = 4;
        
        void (^myBlock)(void) = ^{
            global_i ++;
            static_global_j ++;
            static_k ++;
            NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
        };
        
        global_i ++;
        static_global_j ++;
        static_k ++;
        val ++;
        NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
        
        myBlock();
        
        return 0;
    }
    
    

    运行结果

    Block 外  global_i = 2,static_global_j = 3,static_k = 4,val = 5
    Block 中  global_i = 3,static_global_j = 4,static_k = 5,val = 4
    

    这里就有2点需要弄清楚了
    1.为什么在Block里面不加__bolck不允许更改变量?
    2.为什么自动变量的值没有增加,而其他几个变量的值是增加的?自动变量是什么状态下被block捕获进去的?

    为了弄清楚这2点,我们用clang转换一下源码出来分析分析。

    (main.m代码行37行,文件大小832bype, 经过clang转换成main.cpp以后,代码行数飙升至104810行,文件大小也变成了3.1MB)

    源码如下

    
    int global_i = 1;
    
    static int static_global_j = 2;
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *static_k;
      int val;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *static_k = __cself->static_k; // bound by copy
      int val = __cself->val; // bound by copy
    
            global_i ++;
            static_global_j ++;
            (*static_k) ++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
        }
    
    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[]) {
    
        static int static_k = 3;
        int val = 4;
    
        void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
    
        global_i ++;
        static_global_j ++;
        static_k ++;
        val ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val);
    
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    
        return 0;
    }
    

    首先全局变量global_i和静态全局变量static_global_j的值增加,以及它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,Block结束之后,它们的值依旧可以得以保存下来。

    接下来仔细看看自动变量和静态变量的问题。
    在__main_block_impl_0中,可以看到静态变量static_k和自动变量val,被Block从外面捕获进来,成为__main_block_impl_0这个结构体的成员变量了。

    接着看构造函数,

    
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val)
    
    

    这个构造函数中,自动变量和静态变量被捕获为成员变量追加到了构造函数中。

    main里面的myBlock闭包中的__main_block_impl_0结构体,初始化如下

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
    
    
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = 0;
    impl.FuncPtr = __main_block_impl_0; 
    Desc = &__main_block_desc_0_DATA;
    *_static_k = 4;
    val = 4; 
    

    到此,__main_block_impl_0结构体就是这样把自动变量捕获进来的。也就是说,在执行Block语法的时候,Block语法表达式所使用的自动变量的值是被保存进了Block的结构体实例中,也就是Block自身中。

    这里值得说明的一点是,如果Block外面还有很多自动变量,静态变量,等等,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。

    Block捕获外部变量仅仅只捕获Block闭包里面会用到的值,其他用不到的值,它并不会去捕获。

    再研究一下源码,我们注意到__main_block_func_0这个函数的实现

    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *static_k = __cself->static_k; // bound by copy
      int val = __cself->val; // bound by copy
    
            global_i ++;
            static_global_j ++;
            (*static_k) ++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
        }
    

    我们可以发现,系统自动给我们加上的注释,bound by copy,自动变量val虽然被捕获进来了,但是是用 __cself->val来访问的。Block仅仅捕获了val的值,并没有捕获val的内存地址。所以在__main_block_func_0这个函数中即使我们重写这个自动变量val的值,依旧没法去改变Block外面自动变量val的值。

    OC可能是基于这一点,在编译的层面就防止开发者可能犯的错误,因为自动变量没法在Block中改变外部变量的值,所以编译过程中就报编译错误。错误就是最开始的那张截图。

    Variable is not assignable(missing __block type specifier)
    
    

    小结一下:
    到此为止,上面提出的第二个问题就解开答案了。自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量。上面例子也都证明过了。

    剩下问题一我们还没有解决。

    回到上面的例子上面来,4种变量里面只有静态变量,静态全局变量,全局变量这3种是可以在Block里面被改变值的。仔细观看源码,我们能看出这3个变量可以改变值的原因。

    1. 静态全局变量,全局变量由于作用域的原因,于是可以直接在Block里面被改变。他们也都存储在全局区。


    2. 静态变量传递给Block是内存地址值,所以能在Block里面直接改变值。

    根据官方文档我们可以了解到,苹果要求我们在自动变量前加入 __block关键字(__block storage-class-specifier存储域类说明符),就可以在Block里面改变外部自动变量的值了。

    总结一下在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。

    先来实验一下第一种方式,传递内存地址到Block中,改变变量的值。

    
    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        
      NSMutableString * str = [[NSMutableString alloc]initWithString:@"Hello,"];
        
            void (^myBlock)(void) = ^{
                [str appendString:@"World!"];
                NSLog(@"Block中 str = %@",str);
            };
        
        NSLog(@"Block外 str = %@",str);
        
        myBlock();
        
        return 0;
    }
    
    

    控制台输出:

    Block 外  str = Hello,
    Block 中  str = Hello,World!
    
    

    看结果是成功改变了变量的值了,转换一下源码。

    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      NSMutableString *str;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      NSMutableString *str = __cself->str; // bound by copy
    
                ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_1);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_2,str);
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      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[]) {
        NSMutableString * str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_0);
    
            void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344));
    
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_3,str);
    
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    
        return 0;
    }
    

    在__main_block_func_0里面可以看到传递的是指针。所以成功改变了变量的值。

    至于源码里面的copy和dispose下一节会讲到。

    改变外部变量值的第二种方式是加 __block这个放在第三章里面讨论,接下来我们先讨论一下Block的copy的问题,因为这个问题会关系到 __block存储域的问题。

    二.Block的copy和dispose

    OC中,一般Block就分为以下3种,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock。

    先来说明一下3者的区别。

    1.从捕获外部变量的角度上来看
    • _NSConcreteStackBlock:
      只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
      StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。

    • _NSConcreteMallocBlock:
      有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制

    • _NSConcreteGlobalBlock:
      没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。

    没有用到外部变量肯定是_NSConcreteGlobalBlock,这点很好理解。不过只用到全局变量、静态变量的block也是_NSConcreteGlobalBlock。举例如下:

    
    #import <Foundation/Foundation.h>
    
    int global_i = 1;
    static int static_global_j = 2;
    
    int main(int argc, const char * argv[]) {
       
        static int static_k = 3;
    
        void (^myBlock)(void) = ^{
                NSLog(@"Block中 变量 = %d %d %d",static_global_j ,static_k, global_i);
            };
        
        NSLog(@"%@",myBlock);
        
        myBlock();
        
        return 0;
    }
    
    

    输出:

    <__NSGlobalBlock__: 0x100001050>
    Block中 变量 = 2 3 1
    

    可见,只用到全局变量、静态变量的block也可以是_NSConcreteGlobalBlock。

    所以在ARC环境下,3种类型都可以捕获外部变量。

    2.从持有对象的角度上来看:
    • _NSConcreteStackBlock是不持有对象的。
    
    //以下是在MRC下执行的
        NSObject * obj = [[NSObject alloc]init];
        NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);
        
        void (^myBlock)(void) = ^{
            NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
        };
        
        NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);
        
        myBlock();
    

    输出:

    1.Block外 obj = 1
    2.Block外 obj = 1
    Block中 obj = 1
    
    • _NSConcreteMallocBlock是持有对象的。
    //以下是在MRC下执行的
        NSObject * obj = [[NSObject alloc]init];
        NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount);
        
        void (^myBlock)(void) = [^{
            NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
        }copy];
        
        NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount);
        
        myBlock();
        
        [myBlock release];
        
        NSLog(@"3.Block外 obj = %lu",(unsigned long)obj.retainCount);
    

    输出:

    1.Block外 obj = 1
    2.Block外 obj = 2
    Block中 obj = 2
    3.Block外 obj = 1
    
    • _NSConcreteGlobalBlock也不持有对象
    //以下是在MRC下执行的
        void (^myBlock)(void) = ^{
            
            NSObject * obj = [[NSObject alloc]init];
            NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
        };
        
        myBlock();
    
    

    输出:

    
    Block 中 obj = 1
    

    由于_NSConcreteStackBlock所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈copy到堆。比如当Block作为函数返回值的时候,肯定会copy到堆上。

    1.手动调用copy
    2.Block是函数的返回值
    3.Block被强引用,Block被赋值给__strong或者id类型
    4.调用系统API入参中含有usingBlcok的方法

    以上4种情况,系统都会默认调用copy方法把Block赋复制

    但是当Block为函数参数的时候,就需要我们手动的copy一份到堆上了。这里除去系统的API我们不需要管,比如GCD等方法中本身带usingBlock的方法,其他我们自定义的方法传递Block为参数的时候都需要手动copy一份到堆上。

    copy函数把Block从栈上拷贝到堆上,dispose函数是把堆上的函数在废弃的时候销毁掉。

    
    #define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
    #define Block_release(...) _Block_release((const void *)(__VA_ARGS__))
    
    // Create a heap based copy of a Block or simply add a reference to an existing one.
    // This must be paired with Block_release to recover memory, even when running
    // under Objective-C Garbage Collection.
    BLOCK_EXPORT void *_Block_copy(const void *aBlock)
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    
    // Lose the reference, and if heap based and last reference, recover the memory
    BLOCK_EXPORT void _Block_release(const void *aBlock)
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    
    // Used by the compiler. Do not call this function yourself.
    BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    
    // Used by the compiler. Do not call this function yourself.
    BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
        __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
    

    上面是源码中2个常用的宏定义和4个常用的方法,一会我们就会看到这4个方法。

    
    static void *_Block_copy_internal(const void *arg, const int flags) {
        struct Block_layout *aBlock;
        const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
        
        // 1
        if (!arg) return NULL;
        
        // 2
        aBlock = (struct Block_layout *)arg;
        
        // 3
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }
        
        // 4
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            return aBlock;
        }
        
        // 5
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        
        // 6
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        
        // 7
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        
        // 8
        result->isa = _NSConcreteMallocBlock;
        
        // 9
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        
        return result;
    }
    

    上面这一段是Block_copy的一个实现,实现了从_NSConcreteStackBlock复制到_NSConcreteMallocBlock的过程。对应有9个步骤。

    
    void _Block_release(void *arg) {
        // 1
        struct Block_layout *aBlock = (struct Block_layout *)arg;
        if (!aBlock) return;
        
        // 2
        int32_t newCount;
        newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
        
        // 3
        if (newCount > 0) return;
        
        // 4
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
            _Block_deallocator(aBlock);
        }
        
        // 5
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            ;
        }
        
        // 6
        else {
            printf("Block_release called upon a stack Block: %p, ignored\\\\n", (void *)aBlock);
        }
    }
    

    上面这一段是Block_release的一个实现,实现了怎么释放一个Block。对应有6个步骤。

    上述2个方法的详细解析可以看这篇文章

    回到上一章节中最后的例子,字符串的例子中来,转换源码之后,我们会发现多了一个copy和dispose方法。

    因为在C语言的结构体中,编译器没法很好的进行初始化和销毁操作。这样对内存管理来说是很不方便的。所以就在 __main_block_desc_0结构体中间增加成员变量 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*)和void (*dispose)(struct __main_block_impl_0*),利用OC的Runtime进行内存管理。

    相应的增加了2个方法。

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    

    这里的_Block_object_assign和_Block_object_dispose就对应着retain和release方法。

    BLOCK_FIELD_IS_OBJECT 是Block截获对象时候的特殊标示,如果是截获的__block,那么是BLOCK_FIELD_IS_BYREF。

    三.Block中__block实现原理

    我们继续研究一下__block实现原理。

    1.普通非对象的变量

    先来看看普通变量的情况。

    
    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        
        __block int i = 0;
        
        void (^myBlock)(void) = ^{
            i ++;
            NSLog(@"%d",i);
        };
        
        myBlock();
        
        return 0;
    }
    

    把上述代码用clang转换成源码。

    
    struct __Block_byref_i_0 {
      void *__isa;
    __Block_byref_i_0 *__forwarding;
     int __flags;
     int __size;
     int i;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_i_0 *i; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_i_0 *i = __cself->i; // bound by ref
    
            (i->__forwarding->i) ++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i));
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      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[]) {
        __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
    
        void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
    
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    
        return 0;
    }
    

    从源码我们能发现,带有 __block的变量也被转化成了一个结构体__Block_byref_i_0,这个结构体有5个成员变量。第一个是isa指针,第二个是指向自身类型的__forwarding指针,第三个是一个标记flag,第四个是它的大小,第五个是变量值,名字和变量名同名。

    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
    
    

    源码中是这样初始化的。__forwarding指针初始化传递的是自己的地址。然而这里__forwarding指针真的永远指向自己么?我们来做一个实验。

    
    //以下代码在MRC中运行
        __block int i = 0;
        NSLog(@"%p",&i);
        
        void (^myBlock)(void) = [^{
            i ++;
            NSLog(@"这是Block 里面%p",&i);
        }copy];
    
    

    我们把Block拷贝到了堆上,这个时候打印出来的2个i变量的地址就不同了。

    0x7fff5fbff818
    <__NSMallocBlock__: 0x100203cc0>
    这是Block 里面 0x1002038a8
    

    地址不同就可以很明显的说明__forwarding指针并没有指向之前的自己了。那__forwarding指针现在指向到哪里了呢?

    Block里面的__block的地址和Block的地址就相差1052。我们可以很大胆的猜想,__block现在也在堆上了。

    出现这个不同的原因在于这里把Block拷贝到了堆上。

    由第二章里面详细分析的,堆上的Block会持有对象。我们把Block通过copy到了堆上,堆上也会重新复制一份Block,并且该Block也会继续持有该__block。当Block释放的时候,__block没有被任何对象引用,也会被释放销毁。

    __forwarding指针这里的作用就是针对堆的Block,把原来__forwarding指针指向自己,换成指向_NSConcreteMallocBlock上复制之后的__block自己。然后堆上的变量的__forwarding再指向自己。这样不管__block怎么复制到堆上,还是在栈上,都可以通过(i->__forwarding->i)来访问到变量值。


    所以在__main_block_func_0函数里面就是写的(i->__forwarding->i)。

    这里还有一个需要注意的地方。还是从例子说起:

    //以下代码在MRC中运行
        __block int i = 0;
        NSLog(@"%p",&i);
        
        void (^myBlock)(void) = ^{
            i ++;
            NSLog(@"Block 里面的%p",&i);
        };
        
        
        NSLog(@"%@",myBlock);
        
        myBlock();
    
    

    结果和之前copy的例子完全不同。

    
     0x7fff5fbff818
    <__NSStackBlock__: 0x7fff5fbff7c0>**
     0x7fff5fbff818
    
    

    Block在捕获住__block变量之后,并不会复制到堆上,所以地址也一直都在栈上。这与ARC环境下的不一样。

    ARC环境下,不管有没有copy,__block都会变copy到堆上,Block也是__NSMallocBlock。

    感谢@酷酷的哀殿 指出错误,感谢@bestswifter 指点。上述说法有点不妥,详细见文章末尾更新。

    ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上。

    MRC环境下,只有copy,__block才会被复制到堆上,否则,__block一直都在栈上,block也只是__NSStackBlock,这个时候__forwarding指针就只指向自己了。

    至此,文章开头提出的问题一,也解答了。__block的实现原理也已经明了。

    2.对象的变量

    还是先举一个例子:

    
    //以下代码是在ARC下执行的
    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
         
        __block id block_obj = [[NSObject alloc]init];
        id obj = [[NSObject alloc]init];
    
        NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
        
        void (^myBlock)(void) = ^{
            NSLog(@"***Block中****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
        };
        
        myBlock();
       
        return 0;
    }
    
    

    输出

    
    block_obj = [<NSObject: 0x100b027d0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b03b50> , 0x7fff5fbff7b8]
    Block****中********block_obj = [<NSObject: 0x100b027d0> , 0x100f000a8] , obj = [<NSObject: 0x100b03b50> , 0x100f00070]
    
    

    我们把上面的代码转换成源码研究一下:

    
    struct __Block_byref_block_obj_0 {
      void *__isa;
    __Block_byref_block_obj_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     id block_obj;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      id obj;
      __Block_byref_block_obj_0 *block_obj; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
      id obj = __cself->obj; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , obj , &obj);
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      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[]) {
    
        __attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
    
        id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , obj , &obj);
    
        void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));
    
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    
        return 0;
    }
    

    首先需要说明的一点是对象在OC中,默认声明自带__strong所有权修饰符的,所以main开头我们声明的

    
    __block id block_obj = [[NSObject alloc]init];
    id obj = [[NSObject alloc]init];
    
    

    等价于

    
    __block id __strong block_obj = [[NSObject alloc]init];
    id __strong obj = [[NSObject alloc]init];
    

    在转换出来的源码中,我们也可以看到,Block捕获了__block,并且强引用了,因为在__Block_byref_block_obj_0结构体中,有一个变量是id block_obj,这个默认也是带__strong所有权修饰符的。

    根据打印出来的结果来看,ARC环境下,Block捕获外部对象变量,是都会copy一份的,地址都不同。只不过带有__block修饰符的变量会被捕获到Block内部持有。

    我们再来看看MRC环境下的情况,还是将上述代码的例子运行在MRC中。

    输出:

    
    block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff7b8]
    Block****中********block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff790]
    
    

    这个时候block在栈上,__NSStackBlock__,可以打印出来retainCount值都是1。当把这个block copy一下,就变成__NSMallocBlock__,对象的retainCount值就会变成2了。

    总结:

    在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。
    而在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象,所以才会产生循环引用的问题!

    最后

    关于Block捕获外部变量有很多用途,用途也很广,只有弄清了捕获变量和持有的变量的概念以后,之后才能清楚的解决Block循环引用的问题。

    再次回到文章开头,5种变量,自动变量,函数参数 ,静态变量,静态全局变量,全局变量,如果严格的来说,捕获是必须在Block结构体__main_block_impl_0里面有成员变量的话,Block能捕获的变量就只有带有自动变量和静态变量了。捕获进Block的对象会被Block持有。

    对于非对象的变量来说,

    自动变量的值,被copy进了Block,不带__block的自动变量只能在里面被访问,并不能改变值。


    带__block的自动变量 和 静态变量 就是直接地址访问。所以在Block里面可以直接改变变量的值。


    而剩下的静态全局变量,全局变量,函数参数,也是可以在直接在Block中改变变量值的,但是他们并没有变成Block结构体__main_block_impl_0的成员变量,因为他们的作用域大,所以可以直接更改他们的值。

    值得注意的是,静态全局变量,全局变量,函数参数他们并不会被Block持有,也就是说不会增加retainCount值。

    对于对象来说,

    在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。
    而在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象。

    请大家多多指点。

    更新

    在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSConcreteMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 __NSStackBlock__ 类型的 block 转换为 __NSMallocBlock__ 类型。

    举例如下:

    
    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        
        __block int temp = 10;
        
        NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);});
       
        return 0;
    }
    

    输出

    <__NSStackBlock__: 0x7fff5fbff768>
    

    这种情况就是ARC环境下Block是__NSStackBlock的类型。

    相关文章

      网友评论

      • 89848af90932:很少了解底层,看的头晕,硬着头皮看了一半
        沧州宁少:你可以看下OS X 与iOS 多线程和内存管理。那里面专门有一章讲Block。只不过坦白说,作者写的这个更容易理解
      • Jadyn_Wu:作者你好,我觉得你对//bound to copy这里的解释与我调试的结果不符,我觉得一个局部变量被block所捕获后,是block生成的一个新的变量指针指向了这片内存地址,如果在block外部,这个局部变量被赋值,那么就是指针指向的其他的区域,原来区域的引用计数-1,因为block里面的和外面的指向不同的区域,才会导致值不同,而不是被捕获的时候就是copy了。
        你后面又称,NSMutableString对象为传入指针类型,这里更加有问题了,NSString同样也是指针类型啊,在内部进行appendstring操作,就是在原来内存地址上扩大,这时候block里面捕获到的对象和外部对象内存地址是同一个。
      • 星火燎原:楼主讲解的很详细深入,但却又容易理解,辛苦了
      • GiantAxe77:向楼主学习,自己菜鸟一枚,有个疑问,文中说:“在__main_block_func_0里面可以看到传递的是指针。所以成功改变了变量的值”
        这个在哪里看到传递的是指针啊?
        星火燎原:@GiantAxe 是的
        GiantAxe77:@星火燎原 请问传递的指针是" NSMutableString *str = __cself->str; " 这一句中的*str么?
        星火燎原:static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        NSMutableString *str = __cself->str; // bound by copy
      • 谁知洛神赋:写得是真滴牛b
      • syx_b2ff:这个地方,静态局部变量,在被block捕获的时候,主要是block申明的构造函数__main_block_impl_0的参数,静态局部变量参数传进去的是带&符号的地址,局部变量就是传的值,是这个才决定了block里面捕获变量是指针还是值吧,感觉和取值的地方没什么关系,都是cself->val这样的,只是一个val传进来的是地址,一个是值这个区别。这个和带__block修饰的变量和不太__block修饰的变量是一样的,一个传进去的是带&的变量,就是取的地址,一个就是传进去的值
      • imChay:ARC下,访问外部变量,使用__block修饰,是如何从栈上拷贝到堆上的呢,遵循从栈上拷贝到堆上那几条原则的哪条
        小码农_gjw:@imChay 是的,不访问外部变量的Block是全局Block,并不是栈Block,也就不存在栈Block拷贝为堆Block这一说了
        imChay:ARC下,如果不访问外部变量,是不是即使赋值也不会触发拷贝
      • _阿南_:写的很深啊
      • zziazm:大佬问一下,block被copy到堆上的时候,__block修饰的非对象变量也会copy一份到对上吗?
        sclcoder:使用了__block修饰的普通变量,经过编译器后就成一个对象(结构体)了。这部分你看C++的代码,就可以明白。在生成的对象中有个成员保存了这个费对象变量
      • 9362d9acd9ef:为你的精灵球点赞
      • charlotte2018:#import <Foundation/Foundation.h>

        int main(int argc, const char * argv[]) {

        __block int i = 0;

        void (^myBlock)(void) = ^{
        i ++;
        NSLog(@"%d",i);
        };

        myBlock();

        return 0;
        }
        如果这个block用了copy在arc下,会变成堆block,那么到时候__block int i = 0;局部变量释放了,怎么去找堆里的那个i 啊
        sclcoder:使用了__block修饰的普通变量,经过编译器后就成一个对象(这里假设叫做对象aaa),在生成的对象(aaa)中有个成员(int i)保存了这个非对象变量的值。这个局部变量是的声明周期是和生成的对象aaa一样的。这个对象aaa是被block强引用的,所以不存在你担心的问题
      • AprilThunder:非常好的文章;有一点没弄懂,关于对象的捕获和持有(讨论ARC的情况); 源代码如下:
        __Block_byref_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
        id obj = __cself->obj; // bound by copy
        以上,block_obj 是 by ref, obj 是by copy;前者造成引用,而后者只是简单的copy;
        那么两者都被捕获,但是只有被引用的block_obj是会被强引用;而被copy的obj不会被强引用;
        文中下面的陈述也表明了这个观点:

        根据打印出来的结果来看,ARC环境下,Block捕获外部对象变量,是都会copy一份的,地址都不同。只不过带有__block修饰符的变量会被捕获到Block内部持有。
        在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象,所以才会产生循环引用的问题!

        那么是否说明只有_block修饰的对象才会造成循环引用;
        但是实际上任何被捕获的对象都会造成循环引用;这里怎么理解呢?
        谢谢!
        啊哈呵:我觉得是要分清一些问题:
        __block与循环引用其实是两个问题:
        1、__block修饰局部变量的作用:只有为了能在block里面重新赋值变量,就这么简单(复杂地说是内部代码就给变量变身,__Block_byref_XX对象,然后使用方式XX->forwarding->XX)。

        2、block的循环引用问题,其实与一般代码对象AB互相持有一样,

        为什么很难理解block的循环引用:
        1、只是因为block里面写法表面看不出相互持有,但是block其实也是一个对象,都被表面代码欺骗了
        2、block里面任何使用的对象,都是照搬block定义前对象修饰(strong、weak),
        3、解决循环引用问题,也和一般的AB一样,设置一个weak。

        最后提一点最后strongSelf用法不是block特有的,其实AB的对象相互引用,然后用weak解决也会存在是否需要strongSelf的问题(只是大家不常遇到),
        sunnyxx大神遇到过:http://blog.sunnyxx.com/2015/01/17/self-in-arc/
        一缕殇流化隐半边冰霜:@AprilThunder 并不是任何被捕获的对象都会造成循环引用。如果没有声明__block那么就不会被retain,也就不会存在循环引用的问题了。
      • YY_Lee:因为是通过(i->__forwarding->i)访问的变量,怎么知道原来的forwarding指向了堆上的block呢,即使不指向堆上的,拷贝前后i的地址也不一样啊
        一缕殇流化隐半边冰霜:@Gradient 看forwarding指针指向的内存地址就好了
      • YY_Lee:感觉block的底层实现非常智能,必须捕获的才捕获,必须拷贝的才拷贝,该释放就释放:v:
        一缕殇流化隐半边冰霜:@Gradient 是的。。。设计的很好~~
      • 苏_dcce:hello,霜神,说一个问题,根据你在mrc下做的实验结果
        block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff7b8]
        Block****中********block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff790]

        这里的block_obj在block中和block外的指针地址都是一样的,说明是同一个指针吧?这不能得到
        “在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。”这个结论呀
        一缕殇流化隐半边冰霜:@苏_dcce 嗯嗯。。。看了一下,是同一个
        苏_dcce:@一缕殇流化隐半边冰霜 [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] 你这里后面打印的0x7fff5fbff7e8不是指针的地址么?前面打印的0x100b001b0对象地址说明指针指向相同,后面指针地址相同说明是同一个指针呀。。。。
        一缕殇流化隐半边冰霜:@苏_dcce 这里能说明指针指向相同。。也不能说明是同一个吧。。
      • 三岁就很乖:赞!
        一缕殇流化隐半边冰霜:@三岁就很乖 感谢支持:pray:
      • 92fb1c18dce6:冰霜哥你好,其实这个文章我都看了好几遍,还是有些记不住,是否我哪方便的基础有缺失。:sweat:
        一缕殇流化隐半边冰霜:@kinxcat 理解一下block本质就好了
      • 小包包包:霜神,我有疑问,为啥__block对象,在Block复制到堆时,__block对象,不会被retain,而id对象,则被retain?
        一缕殇流化隐半边冰霜:@小包包包 都是会复制的呀。。对象。。你说的是__block非对象吧???
        小包包包:MRC模式下
      • Sky521:在__main_block_impl_0 这个结构体中,为什么isa都是 _NSConcreteStackBlock?不是说 ARC下,block被赋值,会变成_NSConcreteMallocBlock吗
        2d899c5242bd:@Sky521 此处确实是_NSConcreteStackBlock。也有点疑惑。而且我按如下测试
        -------------------
        void (^IAMBlock)(void) = [^{
        i++;
        } copy];
        i 为全局变量。发现__main_block_impl_0 的isa 还是为impl.isa = &_NSConcreteStackBlock;
        Sky521:@一缕殇流化隐半边冰霜  你文章里的源码就可以看到
        struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        id obj;
        __Block_byref_block_obj_0 *block_obj; // by ref
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
        }
        };
        这个isa难道意思是没赋值前,这个block还是_NSConcreteStackBlock?
        一缕殇流化隐半边冰霜:@Sky521 啊哦,你打印过了这个的类型?
      • 643296560990:冰霜,在GCD代码中有这么一段: dispatch_block_t
        _dispatch_Block_copy(dispatch_block_t db)
        {
        dispatch_block_t rval;

        while (!(rval = Block_copy(db))) {
        sleep(1);
        }
        return rval;
        }

        我想问下如果db为空,那岂不是会进入休眠无法跳出执行return吗?
      • 未之:你好,我想请问一下对于block内部的一个没有被__block修饰的局部变量(变量是一个对象),他在block内部是怎样被处理的呢?
      • CholMay:impl.FuncPtr = __main_block_impl_0; 应该是__main_block_func_0吧,指向实现函数
        一缕殇流化隐半边冰霜:@czc___wjm 不是,这个是源码转换出来的
      • 天口三水羊:楼主的文章都是相当好的总结 持续关注你哦:smile:
      • Chausson:不错
      • hlily:这是我看过写block的最好的一篇博客,思路清晰,赞赞赞
        一缕殇流化隐半边冰霜:@hlily 谢谢!!!有帮助就好!!一起学习!!!:kissing_heart::kissing_heart::kissing_heart:
      • xietao3:果然是深入研究,学习了~
        一缕殇流化隐半边冰霜:@xietao3 一起学习!!:kissing_heart::kissing_heart::kissing_heart:
      • 643296560990:还是说,_block,对于非对象变量,能够使外部变量指向Block内部copy的自身,从而实现block内部也能改变变量值,而对于对象变量,是因为对象被持有,所以能在Block内部改变变量值?那对象变量中,Block内外指针的指向是怎样的呢?
        643296560990:@一缕殇流化隐半边冰霜 那么,可不可以这样理解:在ARC环境下,由于我们会对Block赋值,所以Block是一个mallocBlock,当在Block内部引用的变量,这个变量也会作为Block的一部分被存储在堆上,当在Block内部改变变量值后,因为外部指向内部的变量,所以值也会被改变.而malloc对变量的持有关系,是保证外部变量不会被销毁,能够被正确的拷贝到堆上?
        一缕殇流化隐半边冰霜:@原来我非不快乐 __forwarding的指向,你就关注是不是__NSStackBlock,如果是就是指向自己的,如果不是,就是指向堆上copy那个block
      • 643296560990:ARC环境下,Block中引用的对象变量_forwarding指向呢?和非对象的一样,都指向Block中的自身吗?
        一缕殇流化隐半边冰霜:@原来我非不快乐 Block外的变量指的是??
        643296560990:也就是和非对象变量一样,如果copy到堆上,Block外的变量也就会指向Block内部的自己了,对吗?
        一缕殇流化隐半边冰霜:@原来我非不快乐 是否指向自己还是看block在堆上还是在栈上,在栈上会指向自己。
      • 我叫阿水:objc多线程编程那本书里面讲的
      • 超然自逸:受教了,谢谢
        一缕殇流化隐半边冰霜:@超然自逸 一起学习:kissing_heart::kissing_heart:
      • dongwenbo:向楼主学习!
        一缕殇流化隐半边冰霜:@dongwenbo 请多指教
      • 黎小枫:楼主,厉害啊!学习了
        一缕殇流化隐半边冰霜:@黎小枫 一起学习:kissing_heart::kissing_heart:
      • SmallflyBlog:冰霜好,文章写得很细非常赞,不过这里我还有一个疑问想请教一下下。在 ARC 下,对于 __block 修饰的对象变量,如果捕获它的 Block 没有进行赋值过的话,我打印一下对象地址和指向对象的指针地址,发现捕获前后都相同,和 MRC 下的测试结果一样。也就是说 ARC下,如果 Block 没有进行赋值,就没有被 copy,进而它捕获的 __block 对象变量也没有进行 copy。思路来源于前面的纠正,不知道是不是这样? 下面是测试代码:

        __block id block_obj = [[NSObject alloc]init];
        id obj = [[NSObject alloc]init];

        NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);

        ^{
        NSLog(@"[Block中]block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
        }();
        SmallflyBlog:@一缕殇流化隐半边冰霜 我指是第三章的第二小节:对象的变量。这一小节里面描述捕获 __block 对象变量的情况。应该也是因为赋值过了才 copy __block 对象变量的吧,而不是都会。。。

        原文是:
        > 根据打印出来的结果来看,ARC环境下,Block捕获外部对象变量,是都会copy一份的,地址都不同。只不过带有__block修饰符的变量会被捕获到Block内部持有。
        一缕殇流化隐半边冰霜:@蒋小飞 和你说的一样。。要是不触发copy,bolck就是__NSStackBlock类型。对象地址和指向对象的指针地址,捕获前后应该是相同
        一缕殇流化隐半边冰霜:@蒋小飞 ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上。

        MRC环境下,只有copy,__block才会被复制到堆上,否则,__block一直都在栈上,block也只是__NSStackBlock,这个时候__forwarding指针就只指向自己了。
      • 指尖上的代码:赞赞赞!!!
      • Lotheve:恕我水平太菜有点看晕了,请问博主,对于捕获的非__block类型的外部对象,ARC下不管是栈block还是堆block都会持有该对象,原因在于ARC下对象变量默认是strong修饰的,把外部对象赋值给block结构体中的对象类型的成员变量会使得被赋值对象引用计数+1,也就是被强引用。而在MRC下,对象变量默认不是strong修饰的,被赋值对象不会被强引用,那么这时候对于堆block,为什么同样会持有对象呢?而栈block却不持有?
        一缕殇流化隐半边冰霜:@Lotheve 堆上的block会被copy一下,所以要持有对象
      • 诸葛凌峰:楼主 对象的变量中举得例子 我在MRC下运行了下 堆block并不会持有__block修饰的block_obj对象 block_obj对象的retainCount 仍然是1 这是为什么啊?
        一缕殇流化隐半边冰霜:@雪_晟 一起学习:stuck_out_tongue_winking_eye::stuck_out_tongue_winking_eye:
        雪_晟:撸了一遍,都是精华啊 ,感谢楼主
        一缕殇流化隐半边冰霜:@诸葛凌峰 关于这点你可能需要看看52条里面的36条建议。其实retainCount这个值很不准的,当release的时候也许正好在你打印之前,或者打印之后,有可能你正好打印的时候。。说不准的
      • 简书小王子:果然透彻
        一缕殇流化隐半边冰霜:@简书小王子 一起学习嘛:kissing_heart::kissing_heart:
      • 鼻毛长长:知道原理然后呢?
        一缕殇流化隐半边冰霜:@鼻毛长长 然后就可以了解block的内存管理方面的问题了:stuck_out_tongue_winking_eye::stuck_out_tongue_winking_eye:继续看下篇就知道了
      • DrunkenMouse:ARC下,Block中引用id类型的数据有没有__block都一样都是retain,而对于基础变量而言,没有的话无法修改变量值,有的话就是修改其结构体令其内部的forwarding指针指向拷贝后的地址达到值的修改。而MRC下,则都是拷贝一份指针。是这样吗?我总觉得我得再看几遍。。
        开发全靠xib:@zziazm 日常开发遇到需要引用外部变量的Block 基本都在堆上
        zziazm:文章里有个例子,MRC下对上的block也会retain外部的对象变量,所以是否retain外部的变量是和block在堆上还是栈上有关吧,这样理解对吗
        一缕殇流化隐半边冰霜:@DrunkenMouse 是的,结论就是你说的这样! :+1: :+1:
      • DrunkenMouse:不愧是霜40,拜读完了之后还是看不懂源码 还好有解析 厉害厉害 :+1: :+1:
        一缕殇流化隐半边冰霜:@DrunkenMouse 额。。。仔细看能看懂的。。都是C写的源码。。:kissing_heart::kissing_heart:
      • 405a4e4fb4b0:楼主好厉害
        一缕殇流化隐半边冰霜:@人间一小丑 还是菜鸟:sweat::sweat::sweat:
      • mojue:霜40良心高产博主 (•̀ᴗ•́)و ̑̑
        一缕殇流化隐半边冰霜:@mojue :kissing_heart::kissing_heart::kissing_heart:一起学习,一起进步!!:+1::+1::+1:
      • 三只老虎:markmark
      • 98ab5bc1118d:楼主好厉害:+1::+1::+1:

      本文标题:深入研究Block捕获外部变量和__block实现原理

      本文链接:https://www.haomeiwen.com/subject/qhwosttx.html