《OC高级编程》之 Blocks(二)

Block 的实现

 

Block 的实质

    Block 实质上是 Object-C 对象

    所谓截获自动变量值,意味着在执行 Block 语法时,Block 表达式所使用的自动变量值被保存在 Block 的结构体实例中

(有一、、复杂,随缘看吧)

    Object-C 中由类生成对象,意味着,像结构体这样“生成由该类生成的对象的结构体实例”,生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量 isa 保持该类的结构体实例指针
 

截获自动变量

    Block 语法表达式中使用的自动变量被作为成员变量被追加到结构体中(自动变量的截取只针对 Block 中使用的自动变量)

    所谓“截取自动变量”意味着在执行 Block 语法时,Block 语法表达式所使用的自动变量值被保存到 Block 的结构体实例(即 Block 自身中),但无法实现改写截取变量的值
 

__block 说明符

    想在 Block 中保存值,有两种方法:

  1. 使用静态变量/静态全局变量/全局变量 对静态全局变量和全局变量的访问与转换前完全相同,但静态变量则是通过指针对其进行访问,将其指针传给结构体并保存
  2. 使用 __ block,__ block 变量实际是结构体类型的自动变量,持有指向该实例自身的 __ forwarding 指针,通过 __forwarding 指针来访问成员变量(即原自动变量)
     

Block 存储域

    Block 和 __block 变量实质上都是在栈上的结构体实例,Block 的类别有 _NSConcreteStackBlock,_NSConcreteGlobalBlock,_NSConcreteMallocBlock

    三者分别储存在栈,数据区域(.data区),堆中,目前出现的都是在栈上的,但在记述全局变量的地方使用 Block 时,生成的是 _NSConcreteGlobalBlock 类对象,当 Block 语法表达式中不使用应截获的自动变量时,也会被设置在数据区域

    Block 超出变量作用域可存在的原因:Blocks 提供了将 Block 和 __ block 变量从栈上复制到堆上的方法来解决这个问题,变量作用域结束时,栈上的 Block 和 __ block 被废弃,但堆上的不受影响,而 __ forwarding 指针则指向堆上的结构体实例,使得能够正确访问 __block 变量

    将 Block 作为函数返回值返回时,会自动生成复制到堆上的代码,除此之外,需要使用 copy 方法

    编译器不能进行判断的情况:

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

    但如果在方法/函数中适当复制了传递来的参数,就不需在调用之前手动添加了,以下方法/函数不需要手动复制:

  • Cocoa 框架的方法且方法名中含有 usingBlock 等时

  • Grand Central Dispatch 的 API

Block的类 副本源的配置储存域 复制效果
_NSConcreteStackBlock 复制到堆
_NSConcreteGlobalBlock 数据区域 不做
_NSConcreteMallocBlock 引用计数增加

ARC 有效时多次调用 copy 方法也没有问题

 

__block 变量存储域

__block 变量的配置存储域 Block 从栈复制到堆时的影响
复制到堆并被Block持有
被Block持有

    在栈上的 __ block 变量用结构体实例在 __ block 变量从栈复制到堆上时,会将 __ forwarding 的值替换为复制目标堆上的 __ block 变量用结构体的位置,以达到顺利访问同一个 __block 变量的目的

__block int val = 0;
void (^blk)(void) = [^{++val} copy]; //使用初始化好的 __block 变量
++val;  //使用与Block无关的变量
blk();
NSLog(@"%d", val);
//均可转换为:
//++(val.__forwarding->val);

 

截获对象

    被截取的对象是 Block 用的结构体中附有 __strong 修饰符的成员变量,并通过copy 和 dispose 函数来使 Block 用结构体持有或释放该对象,在 Block 从栈复制到堆时及堆上的 Block 被废弃时会调用这些函数。会复制到堆的情况:

  • 调用 copy
  • 作为函数返回值
  • 将 Block 赋值给附有 __strong 修饰符的 id 或 block 类型时(编译器自动调用 copy)
  • 在方法名中含有 usingBlock 的 Cocoa 框架方法或 Grand Central Dispatch 的 API 中传递 Block 时

    因此,Block 中使用的赋值给附有 __ strong 修饰符的自动变量的对象和赋值到堆上的 __ block 变量由于被堆上的 Block 持有,因而可超出其变量作用域而存在。(只有调用了 copy 函数才能持有捕获的附有 __strong 修饰符的对象类型的自动变量)

    __ block 变量为附有 __ strong 修饰符的 id 类型的自动变量时,和在 Block 中使用赋值给 __strong 修饰符的对象自动变量的对象相同,(通过相似的函数持有和释放)

    如果在 Block 中使用附有 __ weak 修饰符的 id 变量,可正常执行,作用域结束时 nil 被赋值给该变量。若同时指定 __ block 和 __ weak,执行结果一样
 

Block 循环引用

    类对象的 Block 类型成员变量中使用 self 或其他成员变量时会形成循环引用

  1. 可声明 __weak 修饰符的变量,并将 self 赋值使用

  2. 使用 __ block 变量来避免,并在 Block 语法表达式中将 __block 变量赋值 nil

    但如果不执行该 Block,就会循环引用并引起内存泄漏

    优:通过 __block 可控制对象的持有期间

    缺:必须执行 Block

你可能感兴趣的:(《OC高级编程》之 Blocks(二))