一、概念概述
Block对象是一个C级语法和运行时功能。它们与标准C函数类似,除了可执行代码外,它们还可能包含对自动(堆栈)或托管(堆)内存的变量绑定。因此Block可以维护一组状态(数据),它可以用来在执行时影响行为。
你可以使用Block来组合可传递给API的函数表达式,可以选择存储并由多个线程使用。块作为回调特别有用,因为Block带有要在回调中执行的代码以及执行过程中需要的数据。
Block对象为你提供了一种创建特定函数体的方法。
Block是带有扩充功能的自动变量(局部变量)的匿名函数。
二、用法
Blcok代表很小,自包含的代码块。 因此,它们作为封装可能同时执行的工作单元或集合中的部分,或作为其他操作完成时的回调的手段特别有用。
Blcoks是传统回调函数的有用替代方案主要有两个原因:
1. 它们允许你后面在方法实现内部插入编写代码。
因此Blcok通常是框架方法的参数。
2. 它们允许访问局部变量。
你可以直接访问局部变量,而不是使用需要数据结构的回调来实现操作所需的所有上下文信息。
三、Block和变量
变量的类型
在Block对象的代码块内,可以用五种不同的方式处理变量。
你可以引用三种标准类型的变量,就像从函数中引用一样:
- 1.全局变量,包括静态本地变量(static)
- 2.全局函数(这在技术上不是变量)
- 3.来自封闭范围的局部变量和参数
- Block还支持两种其他类型的变量:
1.在函数级别是__block变量。这些在Block(和封闭范围)内是可变的,并且如果任何引用Block被复制到堆中,它们将被保留。
2.const导入。
- Block还支持两种其他类型的变量:
- 5.在方法实现中,Block可以引用Objective-C实例变量( -对象和Block变量)。
Block所在的三种位置
/****__NSGlobalBlock__*****/
void (^block)(void) = ^{
};
NSLog(@"未初始化%@",block);///未初始化
block();
NSLog(@"%@",block);///<__NSGlobalBlock__: 0x106baa148>
static NSInteger d = 15;
void (^block3)(void) = ^{
d++;
NSLog(@"修改后d=%ld",d);
};
d = 19;
block3();
NSLog(@"%@",block3);
/****__NSMallocBlock__*****/
int b = 10;
__block NSInteger c = 15;
void (^block2)(void) = ^{
c = 25;
};
block2();
NSLog(@"修改后c=%ld",c);
NSLog(@"%@",block2);///<__NSMallocBlock__: 0x600000445d30>
/****__NSStackBlock__ 即用__weak修饰时(而用strong、copy修饰的都会自动拷贝到堆区)*****/
int value = 10;
void(^ __weak blockC)(void) = ^{
NSLog(@"value = %d", value);
};
blockC();
NSLog(@"%@", blockC);///<__NSStackBlock__: 0x7ffeec23d9c8>
苹果文档提及,在ARC模式下,在栈间传递block时,不需要手动copy栈中的block,即可让block正常工作。主要原因是ARC对栈中的block自动执行了copy,将_NSConcreteStackBlock类型的block转换成了_NSConcreteMallocBlock的block。
以下规则适用于Block内使用的变量:
全局变量是可访问的,包括封闭词法范围内存的静态变量。
传递给Block的参数是可访问的(就像函数的参数一样)。
栈(非静态)局部变量在Block作用域内访问被捕获为const变量。所以不能被修改。
使用__block存储修饰符声明的局部变量由引用提供,因此是可变的。
任何更改都会反映在封闭词法作用域中,包括在同一个封闭词法作用域中定义的任何其他Block。局部变量在Block的词法范围内声明,其行为与函数中的局部变量完全相同。
每个Block的调用都会提供该变量的新副本。这些变量可以反过来用作Block内封闭块的const或by-reference变量。
注意:
使用Block容易出现循环引用。会造成内存泄漏。解决办法是将其内部所造成相互持有的对象用__weak改为弱引用。
访问修改变量对block结构的影响
访问全局变量(包括全局静态变量):因为全局变量都在静态数据存储区,在程序结束前不会被销毁,是直接访问,没有发生任何改变,为NSGlobalBlock。
访问局部静态变量:这里通过局部静态变量的地址来对其进行修改。为NSGlobalBlock。
访问局部变量:block内部新建同样的一个成员变量用来存储copy外部变量的值,仅相当于一次值传递。所以修改其值是无意义的,所以修改其值系统报错。
访问__block变量:__block会将变量包装成一个对象(Block_byref结构体)。栈中的forwarding指向堆中的拷贝,堆中拷贝的forwarding指针指向它自己,所以修改的都是在堆中的变量。否则无法修改。