Blocks的实现

前言

Blocks的原理,每当本身对知识体系有必定提高以后,再回过头来看一下曾经读过的书籍,会发现对它的理解逐步加深。借着读书笔记活动,立个小目标,把Block完全搞明白,重读《Objective-C高级编程 iOS与OS X多线程和内存管理》第二章节block原理部分,一方面给本身作个笔记,另外一方面加深一下印象。编程

目录

  • Block的实质
  • Block捕获自动变量的值
  • __block的实质
  • Block存储域
  • __block变量存储域
  • 截获对象
  • __block变量和对象
  • Block循环引用

1.block实质

block代码:安全

void (^blk)(void) = ^ {
        printf("Block");
    };
    blk();
复制代码

执行xcrun -sdk iphonesimulator clang -rewrite-objc 源代码文件名就能将含有Block的代码转换为C++的源代码。我是按照书上的示例,一样转换的main.m文件,转换完以后这里就会多出一个main.cpp的文件,打开很恐怖,六万多行... bash

实际上和block相关的代码在最后几十行:

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) {
        printf("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)};
int main(int argc, char * argv[]) {

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}
复制代码

这就是咱们一直在使用的block,由于都是struct结构看上去有点抽象,不过理解起来并不难。多线程

首先先从__main_block_func_0函数开始,由于咱们想要执行的回调看源码都是写在这个函数里面的,block使用的匿名函数(也就是咱们定义的block)实际上被做为简单的C语言函数(block__main_block_func_0)来处理,该函数的参数__cself至关于OC实例方法中指向对象自身的变量self,即__self为指向Block值的变量。__self与OC里面的self相同也是一个结构体指针,是__main_block_impl_0结构体的指针,这个结构体声明以下:框架

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;
  }
};
复制代码

第一个变量是impl,也是一个结构体,声明以下:iphone

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
复制代码

先看FuncPrt,这个就是block括号中函数的函数指针,调用它就能执行block括号中的函数,实际上在调用block的时候就是调用的这个函数指针,执行它指向的具体函数实现。 第二个成员变量是Desc指针,如下为其__main_block_desc_0结构体声明:函数

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
复制代码

其结构为从此版本升级所需的区域和Block的大小。 实际上__main_block_impl_0结构体展开最后就是这样:学习

struct __main_block_impl_0 {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  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;
  }
复制代码

这就是整个__main_block_impl_0结构体所包含的,既然定义了这个结构体的初始化函数,那在详细看一下它的初始化过程,实际上该结构体会像下面这样初始化:ui

isa = &_NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
复制代码

__main_block_func_0这不就是上面说到的那个指向函数实现的那个函数指针,也就是说只须要调用到结构体里面的FuncPtr就能调用到咱们的具体实现了。那这个构造函数在哪里初始化的,看上面的源码是在咱们定义block的时候:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
复制代码

简化为:

struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
复制代码

该源代码将__mian_block_impl_0结构体类型的自动变量,即栈上生成的__mian_block_impl_0结构体实例的指针,赋值给__mian_block_impl_0结构体指针类型的变量blk。听起来有点绕,实际上就是咱们最开始定义的blk__main_block_impl_0结构体指针指向了__main_block_impl_0结构体的实例。

接下来看看__main_block_impl_0结构体实例的构造参数:

__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
复制代码

第一个参数为由Block语法转换的C语言函数指针,第二个参数是做为静态全局变量初始化的__main_block_desc_0结构体实例指针,如下为__main_block_desc_0结构体实例的初始化部分代码:

static struct __main_block_desc_0 __main_block_desc_0_DATA = { 
    0, 
    sizeof(struct __main_block_impl_0)
};
复制代码

__main_block_impl_0结构体实例的大小。

接下来看看栈上的__main_block_impl_0结构体实例(即Block)是如何根据这些参数进行初始化的。也就是blk()的具体实现:

((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
复制代码

简化如下:

(*blk->impl.FuncPtr)(blk);
复制代码

FuncPtr正是咱们初始化__main_block_desc_0结构体实例时候传进去的函数指针,这里使用这个函数指针调用了这个函数,正如咱们刚才所说的,有block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中。blk也是做为参数进行传递的,也就是最开始讲到的__cself。到此block的初始化和调用过程就结束了。

2.Block捕获自动变量的值

基于上面的例子,额外增长个局部变量val:

int val = 0;
 void (^blk)(void) = ^{
    NSLog(@"%d",val);
  };
        
 val = 10;
 blk();
        
复制代码

转换为C++代码以下:

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

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_057be8_mi_0,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, char * argv[]) {
    
    int val = 0;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));

    val = 10;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
复制代码

这与上一节转换的代码稍有差别,自动变量被做为了成员变量追加到了__main_block_impl_0结构体中。在__main_block_impl_0结构体中声明的成员变量类型与自动变量类型彻底相同(block语法表达式中,没有使用的自动变量不会被追加,也就是若是变量没有在block内被使用,是不会被捕获到的)。

另外__main_block_impl_0结构体的构造函数与上一篇也有差别:

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
复制代码

在初始化__main_block_impl_0结构体实例时,自动变量val被以参数的形式传递到告终构体里面,就是在咱们定义block的时候,捕获的自动变量会被用来初始化这个结构体:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));
复制代码

实际上带有这种自动变量的block会像下面这样初始化:

impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
val = 0;
复制代码

由此能够看到,在__main_block_impl_0结构体被初始化的时候,变量val的值被捕获到了而且赋值给了__main_block_impl_0结构体里面的_val成员变量,实际上是值的捕获,并不是内存地址,因此咱们在外部没法修改。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int val = __cself->val; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_057be8_mi_0,val);
        }
复制代码

__cself->val__cself上一篇已经讲过了它指向的就是这个block对象__cself->val就是访问的__main_block_impl_0的成员变量_val,而自动变量的值又赋给了_val,因此咱们在外部改变自动变量的值在block内部是不会生效的。

3.__block的实质

咱们若是想要修改block截获的自动变量的值,静态全局变量,全局变量和静态变量,block内是不会捕获到他们的的,因此这类变量在block内部,是能够进行改写值的。那么他们具体在代码层面上是怎么作的仍是经过上面的命令看一下源码:

int global_var = 1;
static int static_global_var = 2;

int main(int argc, char * argv[]) {
    static int static_var = 3;
    void (^blk)(void) = ^{
        global_var *= 1;
        static_global_var *= 2;
        static_var *= 3;
    };
    
    blk();
    
    return 0;
}
复制代码

通过转换后的源码:

int global_var = 1;
static int static_global_var = 2;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_var;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_var, int flags=0) : static_var(_static_var) {
    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_var = __cself->static_var; // bound by copy

        global_var *= 1;
        static_global_var *= 2;
        (*static_var) *= 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(int argc, char * argv[]) {
    static int static_var = 3;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_var));

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}
复制代码

对静态全局变量static_global_var和全局变量global_var的访问与转换前彻底相同, 那么静态局部变量是如何转换的呢:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_var = __cself->static_var; // bound by copy

        global_var *= 1;
        static_global_var *= 2;
        (*static_var) *= 3;
    }
复制代码

经过int *static_var = __cself->static_var可以看出,其实是将静态局部变量static_var的指针传递给了__main_block_impl_0结构体,这也是超出变量做用域去使用变量的一种方法,那就是经过指针去访问。那为何在局部变量不这么使用呢,这个后面再说,咱们如今只须要知道static修饰的局部变量是能够在block内部进行值的改变的。回归主题,那么自动变量咱们是怎么去修改它的值的,就是经过__block进行修饰,看下代码:

int main(int argc, char * argv[]) {
    __block int var = 10;
    void (^blk)(void) = ^{
        var = 1;
    };
    
    blk();
    
    return 0;
}

复制代码

转换后以下:

struct __Block_byref_var_0 {
  void *__isa;
__Block_byref_var_0 *__forwarding;
 int __flags;
 int __size;
 int var;
};

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

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->var, 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, char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 10};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}
复制代码

不难发现多了一个__Block_byref_var_0结构体实例,它也正是__block的实现。该结构体中的成员变量var就至关于block外面的自动变量的成员变量。而后咱们再看一下block是怎么给这个成员变量进行赋值操做的:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_var_0 *var = __cself->var; // bound by ref

        (var->__forwarding->var) = 1;
    }
复制代码

刚刚在block中给静态变量赋值的时候,使用了指向该静态变量的指针,可是用__block修饰的时候,实际上可以看到__Block_byref_var_0结构体中也就是__block有一个成员变量__Block_byref_var_0 *__forwarding,是一个指向该实例自身的指针,经过成员变量__forwarding就能访问到它自身的var,那么究竟为何要经过这个指向自身的__forwarding来访问成员变量var下一节会说,咱们先知道它就是使用这种方式来访问这个自动变量的。实际上咱们为何能访问到这个成员变量var,是由于在给自动变量定义为__block类型的时候,就会初始化一个__Block_byref_var_0类型的结构体,而且默认将该变量初始化为10(由于咱们给var初始化的10),至关于持有了原自动变量的成员变量。而后在初始化__main_block_impl_0结构体的时候就将这个block结构体做为参数传递了过去,这样__cself->var实际上就是咱们刚才说的初始化的block的结构体,var->__forwarding->var就是访问了这个block的结构体的__forwarding成员变量,__forwarding成员变量指向的又是自身,因此__forwarding->var返回的就是自身的成员变量var,这样整个流程就走通了,具体为何要有个_forwarding咱们继续往下看。

4.Block存储域

经过上面的分析,如今出现了几个问题须要解释:

1.为何要有个_forwarding?(后面说)

2.BLock做用域在栈上,超出变量做用域是否是就销毁了?

上面分析的block和__block都是结构体类型的自动变量,在栈上生成,称为“栈块”,实际上还存在两种block,“堆块”“全局块”。全局块与全局变量同样,设置在程序的.data数据区域,堆块顾名思义,分配在堆上,类型分别以下:

  • _NSConcreteGlobalBlock
  • _NSConcreteStackBlock
  • _NSConcreteMallocBlock

有两种状况,是默认会分配在数据区域上的:

  • 1.记述全局变量的地方有block语法时。
  • 2.block语法的表达式中不使用截获的自动变量的值。

除此以外的Block语法生成的Block为设置在栈上的_NSConcreteStackBlock类对象。配置在全局变量上的Block从变量做用域外也能够经过指针安全的使用,但设置在栈上的Block,若是所属的变量做用域结束,该Block就被废弃。因为__block也配置在栈上,一样的__block变量也会被废弃。Blocks提供了将block和__block变量从栈上复制到堆上的方法来解决这个问题。这样即便block语法记述的变量做用域结束,堆上的block还能够继续存在(原文解释)。大概意思就是有些状况下,编译器会默认对栈block生成一个copy到堆上的操做。大多数状况下,编译器会适当的进行判断是否会将栈块拷贝到堆上,有一种状况除外:

  • 向方法或函数的参数中传递Block。

就是说block做为参数传递的时候是须要咱们手动执行copy的,编译器不会自动执行copy。尽管这样,仍是有两种状况是不须要咱们手动实现,由于他们函数内部已经实现了copy操做:

  • Cocoa框架的方法切方法中含有usingBlock。
  • GCD的API。

举个例子,把书上面的例子本身手动实现了一下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    id object = [self getBlockArray];
    typedef void(^blk_t)(void);
    blk_t blk = (blk_t)[object objectAtIndex:0];
    blk();
}

- (id)getBlockArray {
    int var = 10;
    return [[NSArray alloc] initWithObjects:
            ^{NSLog(@"blk0:%d",var);},
            ^{NSLog(@"blk1:%d",var);}, nil];
}
复制代码

在执行blk()的时候程序异常崩溃了。由于getBlockArray函数执行结束的时候,在栈上建立的block被废弃了,这个时候编译器并无自动执行copy操做,须要咱们手动实现。为何编译器不对全部的栈块都执行copy到堆上,书上明确说明了:block从栈复制到堆上是至关消耗CPU的,将block设置在栈上也可以使用时,将block复制到堆上只是在浪费CPU资源。因此这种状况下对block执行copy就能够了:

- (id)getBlockArray {
    int var = 10;
    return [[NSArray alloc] initWithObjects:
            [^{NSLog(@"blk0:%d",var);} copy],
            [^{NSLog(@"blk1:%d",var);} copy], nil];
}
复制代码
2018-12-24 12:33:33.526163+0800 Blocks-捕获变量的值[54592:3223484] blk0:10
复制代码

文中还提到了若是屡次调用copy会不会有问题,答案固然是没有问题,在ARC下是不用担忧屡次copy引发内存问题。

还有一个_forwarding的问题没有说,至少如今已经知道咱们设置在栈上的block由于执行了copy操做到了堆上,因此咱们无需关心它会超出做用域而被释放的问题了,那么_forwarding继续往下看。

5.__block变量存储域

若是在Block中使用了__block变量,那么该Block从栈复制到堆时,所使用的__block也会被复制到堆上,而且会被Block持有。每个使用了当前__block变量的Block被复制到堆上时都会对这个__block引用计数+1,若是配置在堆上的Block被废弃,相应的它所使用的__block引用计数会-1,直到全部的Block被释放那么此__block也会随之释放。

也就是说,除了上面说到的两种状况,咱们其他的Block基本都会复制到堆上,也就是说咱们使用的__block也会相应的跟着复制到堆上,像OC对象同样,拥有引用计数。那么咱们再分析一下以前遗留的问题,_forwarding是干吗的,当__block被复制到堆上的时候,栈上面的__block结构体里面的_forwarding成员变量就会指向堆里面的__block结构体实例,此时堆上面的__block变量的_forwarding会指向本身自己。也就以下图这个样子:

回顾一下上面__block的实质举过的例子,咱们在用__block修饰自动变量的时候,在func函数里面修改此变量值的时候,经过(var->__forwarding->var) = 1;这种方式去改变的,var->__forwarding实际上访问的是堆上的__block结构体,var->__forwarding->var就是堆里面结构体的var成员变量。这样就算是栈上面的__block被释放了,咱们还能够去访问堆里面的var,这也是为何自动变量不像static静态变量那样经过指针去访问了,由于自动变量在做用域结束以后就会被释放了,拷贝到堆上,做用域结束堆上面还会有其相应拷贝,这份拷贝只有在使用了它的Block释放以后才会释放。

6.截获对象

前面分析了__block修饰的自动变量超出做用域也能使用的原理,实际上对于对象类型,Block对其捕获以后在处理上和__block很像,那么具体使用__block对变量捕获以后当Block和__block被拷贝到堆上和他们被释放这两个过程具体作了什么以前也没有详细讲到,经过捕获对象的学习,也能够对前面作个总结和思考。直接看下书上面的示例代码:

typedef void(^blk_t)(id);
    blk_t blk;
    
    {
        id array = [[NSMutableArray alloc] init];
        blk = [^(id obj){
            [array addObject:obj];
            NSLog(@"array count : %ld",[array count]);
        } copy];
    }
    
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
复制代码
2018-12-25 12:25:06.678625+0800 Blocks-捕获变量的值[56349:3341197] array count : 1
2018-12-25 12:25:06.679199+0800 Blocks-捕获变量的值[56349:3341197] array count : 2
2018-12-25 12:25:06.679210+0800 Blocks-捕获变量的值[56349:3341197] array count : 3
复制代码

按理来讲array在超出变量做用域的时候会被废弃,可是根据打印结果来看一切正常。也就是说array在超出变量做用域后依然存在。经过转换的源码以下:

Block结构体部分:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
  id array = __cself->array; // bound by copy

            ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_1dc794_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
        }
        
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->array, 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};
复制代码

使用Block部分:

typedef void(*blk_t)(id);
    blk_t blk;

    {
        id array = ((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))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
    }

    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
复制代码

这一块我本身测试了一下暂时有点疑问。由于按照书上的示例来看,array前是有__strong修饰的,可是从我转换的源码来看并未看到__strong修饰符。是不是说若是没有使用weak修饰默认为strong?我先默认这个id array是被__strong修饰的。文中讲到,C语言结构体不能附有__strong修饰符的变量,由于编译器不知道应什么时候进行C语言结构体的初始化和废弃操做,不能很好的管理内存。可是它能很好的把握Block从栈复制到堆和把Block从堆上废弃的时机,所以就算Block结构体中含有OC修饰符的变量也同样可以跟随者Block的废弃而废弃。

由于Block结构体中含有__strong修饰符的对象,因此须要对它进行管理,和以前的Block源码对比,在struct __main_block_desc_0结构体中多了两个函数指针:

  • void (copy)(struct __main_block_impl_0, struct __main_block_impl_0*);
  • void (dispose)(struct __main_block_impl_0);

这其实在分析__block原理的时候就有了,实际上他们用处是同样的,都是用来管理Block内存用的。

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
复制代码

_Block_object_assign函数至关于调用了retain函数,将对象赋值在对象类型的结构体成员变量中。

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
复制代码

_Block_object_dispose函数至关于调用了release实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。可是从转换的源码来看,__main_block_copy_0__main_block_dispose_0函数指针都没有被调用,那么它们是在何时触发的?

  • 栈上的Block复制到堆时 --> 触发copy函数。
  • 堆上的Block被废弃时 -->触发dispose函数。

Block被废弃上面说了就是没有对象强引用它就会被回收了,就会调用dispose方法。那么何时栈上的Block会复制到堆上呢?

  • 1.调用Block的copy实例方法。
  • 2.Block做为函数返回值返回时。
  • 3.将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时。
  • 4.在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时。

这样经过__strong修饰符修饰的自动变量,就可以在做用域外使用了。

在前面使用__block的时候实际上这两个函数就已经用到了,略微有点不一样之处:

  • 截获对象时 --> BLOCK_FIELD_IS_OBJECT
  • __block变量时 --> BLOCK_FIELD_IS_BYREF

经过这两个参数用来区分是Block捕获的是对象类型仍是__block变量。除此以外他们在copy和dispose时都是同样的,都是被Block持有和释放。

7.__block变量和对象

对于Block截获__block修饰的变量仍是直接截获对象的处理过程,上面都已经分析完了,包括它们关于内存的处理也都清晰了,惟独使用__block修饰id类型的自动变量尚未说,实际上__block说明符能够指定任何类型的自动变量,固然包括对象类型。仍是按照书上面的例子看下代码:

__block id obj = [[NSObject alloc] init];
复制代码

等同与

__block id __strong obj = [[NSObject alloc] init];
复制代码

经过clang转换以下:

/* __block结构体部分*/
struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id obj;
};
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变量声明部分*/
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {
        (void*)0,(__Block_byref_obj_0 *)&obj,
        33554432,
        sizeof(__Block_byref_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"))
    };
复制代码

这里出现了上一节讲到的_Block_object_assign_Block_object_dispose函数。实际上编译器默认这个obj为__strong类型,当Block从栈复制到堆上时,使用_Block_object_assign函数持有Block截获的对象,当堆上的Block被废弃时,使用_Block_object_dispose函数释放Block截获的对象。这说明使用__block修饰的__strong类型的对象,当__block变量从栈复制到堆上而且在堆上继续存在,那么该对象就会继续处于被持有状态。这与Block中使用赋值给附有__strong修饰符的对象类型自动变量的对象相同。

那么除了__strong,若是用__weak修饰呢?

__weak修饰的对象,就算是使用了__block修饰,同样仍是会被释放掉,实际上书上的源代码也是给了咱们这样一个结论,只不过对象会自动置为nil。而使用__unsafe_unretained修饰时,注意野指针问题。

8.Block循环引用

避免循环引用,根据Block的用途能够选择使用__block变量,__weak修饰符和__unsafe_unretained修饰符来避免循环引用。

__weak和__unsafe_unretained修饰弱引用,不用考虑释放问题。

__block修饰,属于强引用,须要在Block内对修饰的对象置nil,为避免循环引用必须执行Block,可是__block变量能够控制对象的持有时间。

相关文章
相关标签/搜索