Block都继承自NSBlock,并最终继承与NSObject,所以Block有 isa 指针,本质是一个封装了函数调用以及函数调用环境的OC对象。
Block调用实际上是函数调用。
Block分为
- _NSConcreteStackBlock 栈Block
- _NSConcreteGlobalBlock 全局Block
-
_NSConcreteMallocBlock 堆Block
//没有访问 auto 变量, Global block
void (^block1)(void) = ^{
NSLog(@"----------block1");
};
//访问 auto 变量, Stack block (关闭ARC)
int number = 12;
void (^block2)(void) = ^{
NSLog(@"----------block2, %d", number);
};
// 栈 block 调用了 copy
[block2 copy];
block在栈上不定时会被销毁,为了保持栈,调用copy或者对栈属性设置copy关键字,将栈block移到堆内存保存,注意MRC环境下堆内存的release。
Block分类与内存位置
在 ARC 环境下,编译器会根据情況自动将栈上的 block 复制到堆上,比如以下情况:
- block 作为函数返回值时
- 将 block 赋值给 __strong 指针时(block 的 property 设置为 copy, 对应的是这一条,对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。)
- block 作为 Cocoa API 中方法名含有 using Block 的方法参数时
- block 作为 GCD API 的方法参数时
Block 的 copy 操作
Block变量捕获(capture)
为了保证 Block内部能够正常访问外部的变量,block 有个变量捕获机制
1. 对于 基本数据类型 的 局部变量 截获变量的 值,block增加一个变量存储这个局部变量的 值。
局部变量定义 默认是 auto :自动变量 ,如 int number 6;
实际是 auto int number = 6;
,因为默认是 auto,所以一般都是省略的。auto只存在于局部变量,自动变量离开作用域就销毁,为了保证block在执行时依然能获取到值,block只能先捕获自动变量的值。
//基本类型的局部变量截获
- (void)methond0{
int number = 6;
int (^Blockt)(int) = ^int(int num) {
return num * number;
};
number = 1;
NSLog(@"基本类型的局部变量截获:%d", Blockt(2));
}
输出结果:基本类型的局部变量截获:12
2. 对于 静态局部变量 以 指针形式 截获。
因为静态变量常驻内存的,block在使用静态局部变量时只要保存一个指向静态局部变量地址的指针,在需要使用这个变量的时候,通过指针指向的地址,获取最新的值。
//基本类型的静态局部变量截获
- (void)methond1{
static int number = 6;
int (^Blockt)(int) = ^int(int num) {
return num * number;
};
number = 1;
NSLog(@"基本类型的静态局部变量截获:%d", Blockt(2));
}
输出结果:基本类型的静态局部变量截获:2
3. 对于对象类型的局部变量(auto变量) 连同所有权修饰 一起截获。
特别的对于 self,self 是对象在调用函数的时候传入的参数,不同对象调用同一个函数,self参数指代不同的对象。
void (^block)(void) = ^{
NSLog(@"--------%p", self);
};
self 是局部变量,会被捕获
对于类的属性,block会捕获这个类,再去访问这个类的属性,如self有一个 name 属性,下面block依然会捕获self,然后通过self去访问name属性:
void (^block)(void) = ^{
NSLog(@"--------%p", _name);
};
4. 不截获全局变量(包括静态全局变量)。
因为block在任何位置任何时间都可以直接访问到全局变量。
总结:局部变量会捕获,全局变量不会捕获
----------------------------------------------------------------------------------
__block 修饰符
一般情况下,对被截获变量进行 赋值 操作需要添加 __block 修饰符
{
NSMutableArray *array = [NSMutableArray array];
void (^Block)(void) = ^ {
//对数组添加元素,使用数组,不需要__block 修饰符
[array addObject:@1];
};
Block();
}
__block NSMutableArray *array = nil;
void (^Block)(void) = ^ {
//对array初始化赋值,需要__block 修饰符
array = [NSMutableArray array];
};
Block();
- 需要__block 修饰符:对局部变量(包括基本数据类型和对象类型)进行赋值时。
- (void)methond0{
__block int number = 6;
int (^Blockt)(int) = ^int(int num) {
return num * number;
};
number = 1;
NSLog(@"__block修饰的基本类型的局部变量截获:%d", Blockt(2));
}
输出结果:__block修饰的基本类型的局部变量截获:2
__block 修饰的变量最终变成了对象。
-
不需要__block 修饰符:对静态局部变量,静态全局变量,全局变量进行赋值。
__block的内存管理
栈上用__block修饰符修饰的变量在copy之后,在堆上分配了一个与原来变量一样的内存,并且栈上变量的forwarding指针指向堆上block变量的地址;堆上变量forwarding指针也指向自己block变量的地址;
也就是说,在__block变量进行copy之后会在堆上产生一个相同的变量,forwarding指针都指向堆上__block变量。
__forwarding存在的意义:
不论在内存的任何位置,都可以顺利的访问同一个__block 变量。
如果对__block 变量不copy,操作的就是栈上的__block 变量;
如果发生了copy,无论操作的是栈上、还是堆上__block 变量,都是使用的堆上__block 变量。
栈上的Block在变量的作用域结束之后,或者说栈上的函数退出之后销毁。
在MRC环境下,对Block进行copy之后,是否会引起内存泄漏?是的。
随着栈上block和__Block变量作用域的结束,该block和__Block变量会销毁,但是堆上的block或者__block变量没有其它成员变量去指向它,引起内存泄漏。
Block笔试题:
@property(nonatomic, copy)int(^blk)(int);
{
__block int m = 10;
_blk = ^int(int num){
return num * m;
};
m = 6;
[self executeBlock];
}
- (void)executeBlock
{
int result = _blk(4);
NSLog(@"result is %d", res);
}
> result is 24
由于multiplier被__block修饰,在clang编译之后是一个结构体,multiplier是 该结构体的一个成员变量:
在multiplier = 6时,是结构体中forwarding指向的multiplier成员变量赋值为6;
(multiplier.__forwarding->multiplier) = 6;
如果_blk没有进行copy操作,修改的就是栈上的__block变量;反之修改的是堆上__block变量;
__block说明符在MRC和ARC下的区别:
__block说明符的作用是 修饰变量后 可以让变量成为对象,MRC下,__block修饰的变量成为对象后,被block使用后没有强引用的关系,而ARC下有block对__block修饰的变量有强引用的关系;