Objective-C高级编程:iOS与OS X多线程和内存管理(二)

1 Blocks 概要

1.1 什么是 Blocks

Blocks是 C 语言的扩充功能,Blocks 是带有自动变量(局部变量)的匿名函数。

带有自动变量值的匿名函数  分为 “ 匿名函数” 和 “带有自动变量值”

什么是带有自动变量值 

带有自动变量i值的匿名函数。Blocks保持自动变量的值。

2 Blocks 模式

2.1 Block 语法

C 语言函数定义相比,有两点不同 1:没有函数名。 2:带有 ^

第一种:

^ int (int count) { return count + 1;};

第二种: 

省略返回值类型时:Block 语法将按照 return 语句的类型,返回返回值。

第三种: 


2.2 Block 类型变量

“Block” 既指源代码中的 Block 语法,也指由 Block 语法所生成的值。

Block变量声明:

int (^ blk) (int);

Block 类型变量的用途:

(1)自动变量(局部变量)

(2)函数参数

(3)静态变量

(4)静态全局变量

(5)全局变量

使用 Block 语法将 Block 赋值为 Block 类型变量

int (^blk) (int)  = ^(int count) {return count +1 };

在函数参数中使用 Block 类型变量向函数传递 Block:

一种:

void func (int (^blk) (int)) {…}

另一种:

typedef  int (^blk_t) (int);

void func (blk_t blk) {…}

在函数返回值中指定 Block 类型,可以将 Block 作为函数的返回值返回

一种:

- (int (^) (int))func             //注:函数返回值为 Block时,返回类型没有 block 变量名

{

        return ^(int count) {return count + 1;};

}

另一种:

typedef  int (^blk_t) (int);

- (blk_t) func

{

                return ^(int count) {return count + 1;};

}

用 typedef 给 block 重命名 

 用 Block 类型变量调用 Block,与通常的 C 语言变量一样使用:

- (int) methodUsingBlock: (blk_t) blk  rate:(int) rate

{

        return blk(rate);

}

2.3 截获自动变量值

带有自动变量值的匿名函数带有自动变量值 Block 中表现为截获自动亦是值 

Block 截获自动变量

Blocks 中,Block 表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为 Block 表达式保存了自动变量的值,所以在执行 Block 语法后,即使改写 Block 中使用的自动变量的值也不会影响 Block 执行时自动变量的值。

2.4 __block 说明符

自动变量值截获只能保存执行 Block 语法瞬间的值。保存后就不能改写该值。尝试改写截获的自动变量值:会生产编译错误 

若想在 Block 语法的表达式中将值赋给在 Block 语法外声明的自动变量,需要在该自动变量上附加 __block 说明符。

使用附有 __block 说明符的自动变量可在 Block 中赋值,该变量称为 __block 变量。

2.5 截获的自动变量

截获 OC 对象,调用变更该对象的方法:

结论:赋值给截获的自动变量 arrayM 的操作会产生编译错误,但使用截获的值却不会有问题。

Blokc 截获 C 语言数组: 

结论:在 Blocks 中,截获自动变量的方法并没有实现对 C 语言数组的截获。这时,使用指针可以解决该问题。 

3 Blocks 的实现

3.1 Block 的实质

Block的实质即为 Objective-C 的对象(结构体)。

结构体包括:

    isa类结构指针 (三大类型:_NSConcrete[Stack | Malloc | Global ]Block)

    FuncPtr  函数指针

    flags , reserved和 Block 截获的自动变量

clang -rewrite-objc源代码文件名

3.2 截获自动变量值 (只对 Block 中使用的自动变量)

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

3.3 __block 说明符

Block 中修改自动变量的两种方法:

第一种:用 静态局部变量、静态全局变量、全局变量。

注:静态局部变量:Block 结构体中存放静态局部变量的指针。

第二种:用 __block 说明符 (__block 存储域类型说明符) 类似于 static 、auto 、register 说明符,用于指定变量值的存储到哪个存储域中

__block变量clang后转换为结构体, Block 的结构体实例持有指向 __block 变量的结构体实例的指针。

2.3.4 Block 存储域

Block 转换为 Block 的结构体类型的自动变量,__block 变量转换为 __block 变量的结构体类型的自动变量。所谓结构体类型的自动变量,即栈上生成的该结构体的实例。

Block 的类:

_NSConcreteGlobalBlock -存储域:程序的数据区域(.data )

1)记述全局变量的地方使用 Block 语法时

(2)Block 语法的表达式中不使用截获的自动变量时

_NSConcreteStackBlock -存储域:栈 ;复制效果:到堆

除 Global 之外的 Block 生成的 Block 都是栈Block

_NSConcreteMallocBlock -存储域:堆;复制效果:引用计数增加

Blocks提供了将 Block 和 _block 变量从栈上复制到堆上的方法。

这样即使 Block 语法记述的变量作用域结束,堆上的 Block 还可以继续存在。

实际上当 ARC 有效时,大多数情形下编译器会恰当地进行判断,自动生成将 Block 从栈上复制到堆上的代码。

什么时候栈上的 Block 会被复制到堆上:

(1)调用 Block 的 copy 实例方法时

(2)Block 作为函数的返回值时

(2)将 Block 赋值给附有 __strong 修饰符 id 类型的类或Block 类型成员变量时

(3)方法名中含有 usingBlock 的 Cocoa 框架的方法 和 GCD 的 API

需要手动复制的情况:

(1)NSArray 类的initWithObjects

(2)向方法或函数的参数中传递 Block 时(可以不用手动复制) 

不管 Block 配置在何处,用 copy 方法复制都不会引起任何问题。在不确定时调用 copy 方法即可。

3.5 __block 变量存储域

Block中使用 __block 变量,当 Block 从栈复制到堆时,使用的所有 __block 变量也全部被从栈复制到堆。

在多个 Block 中使用 __block 变量   

Block 超出变量作用域可存在的原因:将 Block 和 __block 变量从栈上复制到堆上

__block 变量用结构体成员变量 __forwarding 存在的原因:实现无论 __block变量配置在栈上还是堆上都能正确地进行访问 

3.6 截获对象

__main_block_copy_0 函数使用 _Block_object_assign 函数将对象类型对象赋值给 Block 结构体的成员变量 array 中并持有该对象。

_Block_object_assign 函数调用相当于 retain 实例方法的函数,将对象赋值在对象类型的结构体成员变量中。

__main_block_dispose_0 函数使用 _Block_object_dispose 函数,释放赋值在 Block 用结构体成员变量 array 中的对象。

_Block_object_dispose 函数相当于 release 实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。

__main_block_copy_0 __main_block_dispose_0 函数在 Block 从栈复制到堆时以及堆上的 Block 被废弃时调用

3.7 __block 变量和对象

__block 说明符可指定任何类型的自动变量。

3.8 Block 循环引用

Block 中使用附有 __strong 修饰符的对象类型自动变量,那么当 Block 从栈复制到堆时,该对象为 Block 所持有。这样容易引起循环引用。

解决方法:

1. ARC :通过 __weak 或 __unsafe_unretained 修饰符(iOS4) 来替代__strong 类型的被截获的自动变量。

2. MRC:通过 __block 说明符指定变量不被 Block 所 retain ; ARC下 __block说明符的作用仅限于使其能在 Block 中被赋值。

原理:

如果对 Block 做一次 copy 操作,Block 的内存就会在堆中

它会将所引用的对象做一次 retain 操作

非 ARC : 如果所引用的对象用了 __block 修饰,就不会做 retain 操作

ARC :如果所引用的对象用了 __unsafe_unretained 或 __weak 修饰,就不会做 retain 操作。

3.9 copy/release

ARC无效时,一般需要手动将 Block 从栈复制到堆。也要释放复制的 Block

copy 方法复制 release 方法释放。



注:静态局部变量,静态全局变量,全局变量 的相同与不同点:

相同点:存储区都在全局区

不同点: 作用域不同

全局变量: 其它文件需要用extern关键字再次声明这个全局变量。

    静态全局变量:仅所在的文件才可以访问

    静态局部变量:仅对其所在的函数体作用域可见。

截获 OC 对象,调用变更该对象的方法:

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容