Block原理分析(2)完结

前情提要

基于Block原理分析(1),继续分析Block中的剩余知识点。

1.__block 说明符

我们再来回顾前面截获自动变量的例子。
^{printf(fmt, capturedVariable);};该源码转换结果如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int capturedVariable = __cself->capturedVariable; // bound by copy
printf(fmt, capturedVariable);}

Block中所使用的的被截获自动变量就如"带有自动变量值的匿名函数"所说,仅截获自动变量的值。Block中使用自动变量后,在Block的结构体实例中重写该自动变量也不会改变原来截获的自动变量。详细解释就是截获的自动变量为__cself->fmt。而这个__cself就是blk,而blk结构体的成员变量fmtcapturedVariable是在Block初始化过程中,以值传递的方式赋值给Block结构体的成员变量:

//int main 中
 int unCapturedVariable = 100;
 int capturedVariable = 60;
 const char *fmt = "capturedVariable = %d/n";
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));--->Block;

之后再将初始化好的Block的结构体赋值给blk:
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, capturedVariable));
而下面的函数调用(blk->impl.FuncPtr)(blk)
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
就是把blk当做参数传递给了__main_block_func_0方法,所以__cself就是blk自己。


如果我们试图在Block内部改变被Block截获的值会怎么样呢?系统会产生编译错误。因为在实现上不能改写被截获自动变量值的值,所以当编译器在编译过程中检查出给被截获自动变量值赋值的操作时,便产生了编译错误。这样一来就无法在Block中保存值了,极为不便。解决这个问题有两种方法。

第一种:C语言中有三个变量,允许Block直接改写其值。具体如下:
  • 静态变量: 在函数内定义,相比较于自动变量(生存期为函数的生存期),它的生存期为整个程序。但作用域与自动变量相同;
  • 静态全局变量: 作用域为定义它的文件内,生命周期为定义它文件的生命周期,大部分情况下同源程序;
  • 全局变量: 作用域为全部工程文件, 生命周期为整个源程序的生命周期;
    虽然Block语法的匿名函数部分(__main_block_func_0)变换为了C语言函数,但从这个变换的函数中访问静态全局变量,全局变量并没有任何改变,可直接使用。但是静态变量的情况下,转换后的函数原本就设置在含有Block语法的函数外(静态变量作用域外),所以无法访问, 除非将其捕获。
    我们来看看下面这段源代码:
#include <stdio.h>
// 全局变量
int global_val = 1;
// 静态全局变量
static int static_global_val = 2;
int main() {
    // 静态局部变量
    static int static_val = 3;
    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;
    };
    return 0;
}

转换后,摘取有用的部分:

int global_val = 1;

static int static_global_val = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_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_val = __cself->static_val; // bound by copy
        global_val *= 1;
        static_global_val *= 2;
        (*static_val) *= 3;
    }

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() {

    static int static_val = 3;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
    return 0;
}

这个结果我们已经很熟悉了,对静态全局变量static_global_val和全局变量global_val的访问与转换之前完全相同。静态变量static_val又是如何转换的呢?以下摘出Block中使用该变量的部分:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy
        global_val *= 1;
        static_global_val *= 2;
        (*static_val) *= 3;
    }

那这个__cself->static_val又是谁呢?

int main() {
    static int static_val = 3;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
    return 0;
}

Block初始化的时候的一个参数&static_val。进行了一个指针传递,这是超出作用域使用变量的最简单方法。可以自己用printf打印再确认一下。实际上,在由Block语法生成的值Block上,可以存有超过其变量作用域的被截获对象的自动变量。变量作用域结束的同时,原来的自动变量被废弃,因此Block中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量。但该变量在Block内仍然可以访问,这种情况发生在将Block从栈复制到堆上,与此同时__block修饰的变量也会一同被复制到堆上, Block持有该对象。

第二种:使用"__block说明符"。更准确的表述是:"__block存储类域说明符"(这个类不是面向对象的类,是类域)。C语言中有以下存储类域说明符:typedef,extern,static,auto,register

__block说明符类似于static,autoregister说明符,他们用于指定将变量值设置到哪个存储域中,例如,auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区内。
下面我们来实际使用__block说明符,用它来指定Block中想变更值的自动变量。
.m代码:

#include <stdio.h>
int main() {
    __block int val = 10;
    void (^blk)(void) = ^{
        val = 1;
    };
    blk();
    return 0;
}

转换后,摘取又用的部分:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref

        (val->__forwarding->val) = 1;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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() {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}

加一个__block相较于之前转换的代码出现了很多新面孔,我们一个一个地看。
__block int val = 10;转换后的代码是:

__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
        (void*)0,
        (__Block_byref_val_0 *)&val,
         0,
         sizeof(__Block_byref_val_0),
         10};

整体简化一下:

 __Block_byref_val_0 val = {
        (void*)0,
        (__Block_byref_val_0 *)&val,
         0,
         sizeof(__Block_byref_val_0),
         10
};

我们发现,这个自动变量变成了一个结构体实例。__block修饰的变量(val)也同Block一样变成__Block_byref_val_0结构体类型的自动变量,即栈上生成的__Block_byref_val_0结构体实例,该变量初始化为10,并且这个值(10)也出现在了结构体实例的初始化中,这意味着该结构体持有相当于原自动变量(val)的成员变量(int val)。
该结构体声明如下:

struct __Block_byref_val_0 {
  void *__isa;
  __Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

接着,给__block变量赋值的代码:^{val = 1;};转换后:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        __Block_byref_val_0 *val = __cself->val; // bound by ref
        (val->__forwarding->val) = 1;
    }

刚刚在Block中向静态变量赋值时,使用了指向该静态变量的指针,而向__block变量赋值要比这个更为复杂。Block__main_block_impl_0结构体实例持有指向__block变量的__Block_byref_val_0结构体实例指针(&val)。
__Block_byref_val_0结构体实例的成员变量__forwarding持有指向该实例自身的指针(看初始化函数,__forwarding的赋值就是&val)。通过成员变量__forwarding访问成员变量val。(成员变量int val是该实例自身持有的变量,它相当于原自动变量val)。
究竟为什么会有成员变量__forwarding呢?是为了保证,无论这个被捕获的变量是在栈上还是堆上都能正确地访问这个变量(唯一性)。
另外,__block变量的__Block_byref_val_0结构体实例attribute((blocks(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 )&val, 0, sizeof(__Block_byref_val_0), 10};的初始化并不在Block的__main_block_impl_0结构体中,而是在int main中这样做是为了在多个Block中使用__block变量。有如下.m源码:

#include <stdio.h>
int main() {
    __block int val = 20;
    void (^blk0)(void) = ^{
        val = 0;
    };
    void (^blk1)(void) = ^{
        val = 1;
    };
    blk0();
    blk1();
    return 0;
}

转换后:

__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 20};
    void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    void (*blk1)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (__Block_byref_val_0 *)&val, 570425344));

初始化blk0,blk1的代码,都是直接&val(拿的地址),而这个val是上面创建的__Block_byref_val_0结构体实例
两个Block都使用了__Block_byref_val_0结构体实例val的地址(&val),这样就可以在多个Block中使用同一个__block变量了。当然,反过来一个Block也可以使用多个__block变量,比如.m:

__block int val = 20;
    __block int val1 = 20;
    __block int val2 = 20;
    void (^blk0)(void) = ^{
        val = 0;
        val1 = 0;
        val2 = 0;
    };

转换后:

__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 20};
    __attribute__((__blocks__(byref))) __Block_byref_val1_1 val1 = {(void*)0,(__Block_byref_val1_1 *)&val1, 0, sizeof(__Block_byref_val1_1), 20};
    __attribute__((__blocks__(byref))) __Block_byref_val2_2 val2 = {(void*)0,(__Block_byref_val2_2 *)&val2, 0, sizeof(__Block_byref_val2_2), 20};
    void (*blk0)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, (__Block_byref_val1_1 *)&val1, (__Block_byref_val2_2 *)&val2, 570425344));

下面我们来了解Block超出变量作用域仍可存在的理由,以及__block变量的结构体成员变量__forwarding存在的理由。讨论之前,我们先来看一下Block的存储域。

  • 通过前面说明可知,Block(^{printf("This is a Block")};)转换为Block的结构体类型(__main_block_impl_0)的自动变量,__block变量(__block int val = 20;)转换为__block变量的结构体类型(__Block_byref_val_0)的自动变量。所谓的结构体类型的自动变量,就是栈上生成的该结构体的实例。另外,通过之前的说明可知Block也是OC对象。将Block当做OC对象来看时,该Block的类为_NSConcreteStackBlockOC有很多与之类似的类,如:_NSConcreteStackBlock,_NSConcreteGlobalBlock,_NSConcreteMallocBlock
  • 首先我们能够注意到_NSConcreteStackBlock中含有stack一词,即该类的对象Block设置在栈上。同样地,_NSConcreteMallocBlock类对象设置在由mallo函数分配的内存块即堆中,而_NSConcreteGlobalBlock类对象则设置在程序的数据区域(.data区)中。
  • 到现在为止出现的Block例子使用的都是_NSConcreteStackBlock类,且都设置在栈上,但实际上并非全是这样,在记述全局变量的地方使用Block语法时,生成的Block_NSConcreteGlobalBlock类对象,例如:void (^blk)(void) = ^{printf("Global Block\n")};转换后的impl.isa = &_NSConcreteGlobalBlock;,该Block的类为_NSConcreteGlobalBlock类。此Block设置在程序的数据区域内。因为在使用全局变量的地方不存在自动变量,所以不存在对自动变量进行截获一说。也就是说Block用结构体实例的内容不依赖于执行时的状态,所以整个程序中只需一个实例。因此,只要不需要用Block来截获自动变量,就可以将Block用结构体实例设置在程序的数据区域内。

那么在Block配置在堆上的_NSConcreteMallocBlock类对象是在何时使用的呢?这正是上一节最后遗留问题的答案。遗留问题为:Block超出变量作用域可存在的原因,和__block变量用结构体成员变量__forwarding存在的原因。

  • 配置在全局变量上的Block,从变量作用域外也可以通过指针安全地使用。但设置在栈上的Block。如果其所属的变量作用域结束,该Block就被废弃。由于__block变量也配置在栈上,同样地,如果其所属的变量作用域结束。则该__block变量也会被废弃。
  • Blocks提供了将__block变量从栈上复制到堆上的方法来解决这个问题。将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。
  • 复制到堆上的Block_NSConcreteMallocBlock类对象写入Block用结构体实例的成员变量isaimpl.isa = &_NSConcreteMallocBlock;
  • __block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上,都能够正确地访问__block修饰的变量。有时在__block变量配置在堆上的状态下,也可以访问栈上的__block变量。在此情形下,只要栈上的结构体成员变量__forwarding指向堆上的结构体实例,那么不管是从栈上的__block变量还是从堆上的__block变量都能正确访问。
  • 那么Blocks提供的复制方法究竟是什么呢?实际上当ARC有效时,大多数情形下编译器会恰当地进行判断,自动生成Block从栈上复制到堆上的代码。
    例:
typedef int (^blk_t)(int);
blk_t testFunc(int rate) {
    return ^(int count){return rate * count;};
}

非ARC下编译是不能通过的,会提示:error: returning block that lives on the local stack return ^(int count){return rate * count;};
而ARC环境下的代码会被编译为:

blk_t testFunc(int rate) {
    blk_t tmp = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate);
    tmp = objc_retainBlock(tmp);
    return objc_autoreleaseReturnValue(tmp);
}

objc4运行时库可知,objc_retainBlock函数实际上就是__Block_copy函数。

仔细分析这三步都发生了什么:

1.blk_t tmp = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate);将通过Block语法生成的Block(&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, rate))赋值tmp
2.接下来tmp = objc_retainBlock(tmp);相当于tmp = __Block_copy(tmp);就是把栈上的tmp也就是原来生成的Block结构体实例,复制到堆上,再把堆上的这个Block的地址赋值给tmp。现在tmp所指的就是堆上的Block结构体实例。
3.最后return objc_autoreleaseReturnValue(tmp);将堆上的结构体实例作为对象,注册到autoreleasepool中然后返回该对象。即,ARC下将Block作为函数返回值,编译器会自动生成复制到堆上的代码。


那什么情况下是编译器不能恰当判断的呢?

就是向方法或函数的参数中传递Block时。但如果在方法或函数中适当地复制了传递过来的参数(Block),那么就不必再调用该方法或函数手动复制了。比如Cocoa框架的方法且方法名中含有usingBlock的,以及GCDAPI。其他情况下要记得加copy,([XXBlock copy];)。另外对于已配置在堆上和数据区域的Block调用copy方法是,前者引用计数会加1,后者什么也不做。但无论Block配置在何处,用copy方法都不会引起任何问题。在不确定时,调用copy方法即可。在ARC下即便多次调用copy,编译器也能正确地控制引用计数,所以没问题。


__block变量存储域

若在一个Block中使用__block变量,则当Block从栈上复制到堆上时。这些__block变量也全部被从栈复制到堆。此时,Block持有__block变量。即使在该Block已复制到堆上的情形下,复制Block也对所使用的__block变量没有任何影响。在多个Block中使用__block变量时,因为最先会将所有的Block配置在栈上,所以__block变量也会配置在栈上。在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被该Block所持有。当剩下的Block从栈复制到堆时,被复制的Block持有__block变量,并增加__block变量的引用计数。如果配置在堆上的Block被废弃,那么它所使用的__block变量也就被释放。

聊聊__forwarding的作用:不管__block变量配置在栈上还是在堆上,都能够正确地访问该变量。看如下.m代码:

    __block int val = 0;
    void (^blk)(void) = [^{++val;} copy];
    NSLog(@"%d", val);
    blk();
    NSLog(@"%d", val);
    //外层++val
    ++val;
    NSLog(@"%d", val);

输出是0 1 2。
转换后的代码:

//初始化__block变量的结构体实例:
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
            (void*)0,
            (__Block_byref_val_0 *)&val,
             0,
             sizeof(__Block_byref_val_0),
             0
            };
//初始化Block用结构体并copy再赋值给blk:
void (*blk)(void) = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344)), sel_registerName("copy"));
//执行blk的方法:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
//blk中的方法:
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
    __Block_byref_val_0 *val = __cself->val; // bound by ref
    ++(val->__forwarding->val);
    }
//执行外层val++:
++(val.__forwarding->val);
//最后打印val:
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xh_7qhjzbrx7zz361c_rtp2lh0w0000gn_T_ViewController_937775_mi_0, (val.__forwarding->val));

当初始化__block结构体实例的时候,__forwarding指向的是他自己(val栈);copy的时候,会把Block复制到堆上,同时也把__block结构体实例复制到堆上,并且栈上的__block的成员变量__forwarding会指向堆上的自己(val堆)。执行Block内部的++(val(栈)->__forwarding->val(堆))。执行外层val++:++(val(栈)->__forwarding->val(堆))。打印:val(栈).__forwarding->val(堆)。通过该功能,无论是在Block语法中,Block语法外使用__block变量,还是__block变量配置在栈上或者堆上,都可以顺利访问同一个__block变量。


截获对象

.m代码:

typedef void (^blk_t)(id);
    blk_t blk;
    {
        id testArray = [NSMutableArray new];
        blk = [^(id obj){
            [testArray addObject:obj];
            NSLog(@"%lu", (unsigned long)[testArray count]);
        } copy];
        
    }
    blk([NSObject new]);
    blk([NSObject new]);
    blk([NSObject new]);

转换后,截取有用的代码:

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  id testArray;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, id _testArray, int flags=0) : testArray(_testArray) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ViewController__viewDidLoad_block_func_0 (struct __ViewController__viewDidLoad_block_impl_0 *__cself, id obj) {
  id testArray = __cself->testArray; // bound by copy

            ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)testArray, sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_xh_7qhjzbrx7zz361c_rtp2lh0w0000gn_T_ViewController_c6f3a9_mi_0, (unsigned long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)testArray, sel_registerName("count")));
        }

static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->testArray, (void*)src->testArray, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->testArray, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
//使用部分:
typedef void (*blk_t)(id);
    blk_t blk;
    {
        id testArray = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
        blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, testArray, 570425344)), sel_registerName("copy"));

    }
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));

OC的运行时库能够准确把握Block从栈复制到堆以及堆上的Block被废弃的时机,使用__ViewController__viewDidLoad_block_desc_0结构体中增加的成员变量copydispose,以及作为指针赋值给该成员变量的__ViewController__viewDidLoad_block_copy_0函数和__ViewController__viewDidLoad_block_dispose_0函数。__ViewController__viewDidLoad_block_copy_0函数使用_Block_object_assign函数将对象类型对象赋值给Block用结构体的成员变量testArray中并持有该对象。用来管理赋值给Block用结构体中的testArray的对象。相当于retain实例方法,将对象赋值在对象类型的结构体成员变量中。另外_Block_object_dispose用来释放testArray中的对象,相当于release实例方法,释放赋值在Block用结构体成员变量testArray中的对象。在Block从栈上复制到堆时,以及堆上的Block被废弃时才会调用这两个函数。

Block复制到堆上的时机:

- 调用Blockcopy方法;

- Block作为函数返回值时;

- 将Block赋值给附有__strong修饰符id类型的类或是Block类型成员变量时;

- 在方法名中含有usingBlockCocoa框架方法或GCDAPI中传递Block时;

总结

_Block_copy函数被调用时Block被从栈上复制到堆上。通过这种方式,Block截获的对象就能够超出其变量作用域而存在。在Block中使用__block时,也会有这两个方法,不同的是截获对象是:BLOCK_FIELD_IS_OBJECT,截获__block自动变量是:BLOCK_FIELD_IS_BYREF。通过这两个参数区分对象类型还是__block变量。由此可知,Block中使用的赋值给附有__strong修饰符的自动变量的对象和复制到堆上__block变量由于被堆上的Block所持有,因而可以超出其变量作用域而存在。


附加:Block的循环引用

typedef void (^blk_t)(void);
@interface MyObject: NSObject {
    blk_t blk;
}

@implementation MyObject
- (id)init
{
  self = [super init];
  blk_ = ^{NSLog(@"self = %@", self)};
  return self;
}

- (void)dealloc {
  NSLog(@"dealloc");
}

int main() {
  id o = [[MyObject alloc] init];
  NSLog(@"%@", o);
  return 0;
}

Block赋值给了对象的成员变量blk_,相当于该Block自动调用了copy方法,Block被从栈复制到堆上,而且Block还截获了__strong修饰符修饰的self对象,self一并被复制到堆上并被Block所持有。因此,对象持有blk_(Block)Block持有对象(MyObject),形成了循环引用,此时,不会调用对象的dealloc方法。为避免这种循环引用,id __weak tmp = self,之后Block中使用tmp就没问题了。
另外也可以使用__block变量来避免循环引用,但需要在Block体中,给该变量赋值nil。并执行改Block。如不执行Block,将该变量赋值为nil,仍然会引发循环引用。Block持有__block变量,__block变量持有self,self持有Block。若执行了__block变量等于nil,__block变量就不持有self了,从而打破循环。
使用__block打破循环的优点在于执行Block时可动态决定是否将nil或是其他对象赋值在__block变量中。缺点是,为避免循环引用必须要执行Block

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351