OC Block

 

Block (代码块)

Block是iOS 4 引入的C语言扩展功能,实质上是带有自动变量(局部变量)的匿名函数。

如下,函数的定义是必须有名字的,函数可以通过函数指针来传递和调用;Block的定义不需要名字,但是需要赋给Block变量才可以使用

/**
 *
 * 函数的定义必须是有名字的
 **/
int func(int count)
{
    printf("%d",count);
    return count;
}

int (*funcpter)(int) = &func;

(*funcpter)(10);

**
  *
  * block的定义不需要名字,然后赋给一个block变量
  **

  int (^testBlock)(int) = ^int(int count){
    printf("%d",count);
    return count;
  }

  testBlock(10);


我们知道在函数的函数体中可以访问以下的变量:

  • 在函数体中定义的局部变量
  • 函数的参数
  • 静态变量
  • 静态全局变量
  • 全局变量

而在Block的闭包中,不仅可以访问以上的变量,还可以访问在定义Block的闭包中可以访问的一切变量(包括self,局部变量,实例变量).这些变量就是Block可以访问的自动变量.

如下图:在block中访问了局部变量,self,实例变量

如上图,打印了block中访问自动变量的地址,只有局部变量的地址是相同的。因此Block访问这些变量是将变量值做了一个浅拷贝,这样就没有办法修改原有的变量了。即截获自动变量的瞬时值

Block的实现

block_实现1

如上图所示,我们在main函数中定义了一个block。接下来,我们用clang编译器命令(clang -rewrite-objc main.m)将OC的代码翻译为C语言的实现。

C语言实现如下:

__main_block_impl_0

Block的定义翻译为__main_block_impl_0这个结构体变量的定义。__main_block_impl_0的结构如下:

__main_block_impl_0结构体中包括__block_impl结构体变量,__main_block_desc_0结构体指针,构造函数以及e,str,c等其他变量。

__block_impl中保存的是Block的类型以及具体实现(函数指针);__main_block_desc_0中记录的是block的附加信息;e,str,c等变量保存的是block捕获的自动变量,在ARC中是强引用。

__block_impl

__block_impl结构体如下:

可以看到__block_impl结构中包含isa变量,保存block的类型,所以block是一个OC对象;

FuncPtr是一个函数指针,指向Block的具体实现。可以看到该函数的参数不只有block的参数,还有一个__main_block_Impl_0指针,通过这个指针函数可以访问block的自动变量。

__main_block_desc_0

__main_block_desc_0结构如下,主要记录block的附加信息,包括结构体的大小,需要capture和dispose的变量列表

自动变量

Block捕获的自动变量,会保存的在__main_block_impl结构体中。

  • 对于基本数据类型,是将值拷贝到__main_block_impl结构体中,所以在Block中就无法访问原来的变量了。

  • 在ARC中,捕获的OC对象是通过强引用保存到__main_block_impl结构体中,因此需要注意引用循环的问题。

  • __block变量实质上是一种结构体,结构如下:其中__forwarding保存的是__block变量的地址,c保存的是__block变量的值。__main_block_impl中会保存该结构体的指针。

Block的类型以及内存管理

在 OC中,总共由三种block类型:

  1. _NSGlobalBlock,全局的静态block,不会访问任何的外部变量,它的生命周期是全局的。

  2. _NSStackBlock,保存在栈上的block,当定义block的闭包结束,就会被销毁

  3. _NSMallocBlock,保存在堆上的block,当引用计数为0时,会被销毁。

_NSGlobalBlock

不访问任何自动变量的Block都是_NSGlobalBlock类型,且类型不会改变。

_NSStackBlock

访问自动变量的block在定义时默认是_NSStackBlock类型,只有调用了copy类型,才会变成_NSMallocBlock类型。

_NSMallocBlock

在ARC中,当Block被定义后,传给一个OC变量,会自动调用Block的copy方法,这个时候Block就会变为_NSMallocBlock类型。在MRC,则需要手动调用Copy

避免循环引用

由于Block在访问自动变量时,比较容易造成循环引用。对于这个问题,需要将引用的一方变为weak,避免循环引用。

__weak __typeof__(self) weakSelf = self;    // 将引用一方变为weak

NSBlockOperation *op = [[NSBlockOperation alloc] init];
[ op addExecutionBlock:^ {

    __strong __typeof__(self) strongSelf = weakSelf;
    // 在这里强引用一下weakself,避免在block执行过程中,weakself被销毁
    [strongSelf doSomething];
    [strongSelf doMoreThing];
} ];
[someOperationQueue addOperation:op];

原文章地址

https://github.com/ExistOrLive/DocumentForLearning/blob/master/iOS/Objective_C/Block/Block.md