Block是将(函数)及其(执行上下文)封装起来的(对象)
// MCBlock.m
- (void)method
{
int multiplier = 6;
int (^Block)(int) = ^int(int num) {
return num * multiplier;
};
Block(2);
}
源码解析,使用 clang -rewrite-objc file.m 命令查看编译之后的文件内容
结构体构造函数中第一个参数是函数指针,第二个参数block的描述,第三个参数是block中使用的局部变量,第四个参数标记,: multiplier(_multiplier)表示将参数_multiplier直接赋值给结构体中的multiplier
传递的参数是block本身和num
{
int multiplier = 6;
int (^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
Block(2); => "result is 12"
}
block截获变量
对(基本数据)类型的(局部变量)截获(其值)
对(对象)数据类型的局部变量(连同所有权修饰符)一起截获
以(指针形式)截获局部静态变量
不截获全局变量和静态全局变量
// 全局变量
int global_var = 4;
// 静态全局变量
static int static_global_var = 5;
- (void)method
{
// 基本数据类型的局部变量
int var = 1;
// 对象类型的局部变量
__unsafe_unretained id unsafe_obj = nil;
__strong id strong_obj = nil;
// 局部静态变量
static int static_var = 3;
void(^Block)(void) = ^{
NSLog(@"局部变量<基本数据类型> var %d", var);
NSLog(@"局部变量<__unsafe_unretained 对象类型> unsafe_obj %@", unsafe_obj);
NSLog(@"局部变量<__strong 对象类型> strong_obj %@", strong_obj);
NSLog(@"局部静态变量 %d", static_var);
NSLog(@"全局变量 %d", global_var);
NSLog(@"全局静态变量 %d", static_global_var);
};
Block();
}
__block修饰符
在什么场景下,需要使用__block修饰符呢
一般情况下,对被截获变量进行(赋值,其中赋值不等于使用)操作需添加__block修饰符
笔试题的坑
{
NSMutableArray *array = [NSMutableArray array];
void (^Block)(void) = ^{
[array addObject:@123]; //这种场景只是使用没有赋值不需要加__block
};
Block();
}
{
__block NSMutableArray *array = nil;
void (^Block)(void) = ^{
array = [NSMutableArray array]; // 进行了初始化和创建记赋值了要加__block
};
Block();
}
对变量进行赋值操作时
需要__block修饰符 => 局部变量:基本数据类型和对象类型
不需要__block修饰符 => 静态局部变量(截获是以指针形式),全局变量(不截获),静态全局变量(不截获)
__block相关笔试题
{
__block int multiplier = 6;
int (^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
Block(2); => result is 8
}
__block修饰的变量变成了对象
__forwarding指针是用来干什么的?
__forwarding指针本身指向自己,那为什么需要这个指针(没有指针的情况下,也可以直接通过成员变量的访问对mutiplier的值进行赋值)
Block的内存管理
何时对block进行copy操作
比如声明一个对象的成员变量是一个block,而在栈上去创建block并赋值给成员变量block,如果成员变量block没有使用copy关键字,而是使用assign的话,那么当具体通过成员变量访问block的话,可能就由于栈所对应的函数退出之后在内存当中就销毁掉了,这个时候继续访问block就崩溃了
对栈上的__forwarding指针进行copy操作之后,那么栈上的__forwarding指针指向的是堆上的__block变量,而堆上的__forwarding指针指向的是其自身,所以在对multiplier进行值改变的时候使用同一行代码对无论是栈上还是堆上的都是生效的
比如我们在栈上面对某个__block修饰的值进行修改,如果我们对栈上的__block变量已经做过copy操作之后,实际上我们修改的不是栈上__block变量的对应的值,而是通过栈上__block变量中的forwarding指针找到堆上面的block变量,然后对堆上的mutiplier进行值修改,同样的
如果__block变量由于被成员变量的block所持有的话,当我们在另一个方法或者其他地方去调用__block修改的情况下,实际上是自身一个forwarding指针进行修改的,所以经过编译器编译,无论是栈上的还是堆上的调用都是针对堆上的进行修改的,大前提是栈上的__block经过了copy在堆上面产生了一致的__block变量,如果说我们没有对栈上的__block变量做过copy操作,那么修改的是栈上的block变量.
__forwarding存在的意义
不论在任何内存位置,都可以顺利访问同一个__block变量
我们没有对__block进行copy的话,实际上操作的是栈上的__block变量
如果对__block进行copy之后,无论是在栈上还是堆上,我们对__block的修改和赋值操作实际上操作的都是堆上的__block变量
同时栈上关于__block变量的使用,也都是使用的堆上的__block变量
Block的循环引用
{
_array = [NSMutableArray arrayWithObject:@"block"];
_StrBlk = ^NSString *(NSString *num) {
// 对象类型的局部变量或者成员变量,在block的copy修饰后, 由于array是用strong关键字修饰,所以block中有strong类型的指针指向
// self
return [NSString stringWithFormat:@"helloc_%@",_array[0]];
};
_strBlk(@"hello");
}
如何解决
为什么用__weak就可以避免循环引用,
因为block截获变量如果是对象类型是连同其所有权修饰符一并进行截获,如果在外部是__weak所有权修饰符,那么在
block结构体中中所有权修饰符也是__weak
在MRC下,不会产生循环引用
在ARC下,会产生循环引用,引起内存泄漏
对象有一个成员变量持有block,而block中使用到了__block修饰的变量,所以block对象持有__block变量
而__block本身也对原对象强引用持有,因为__block变量的指向是原来的对象