本文主要内容
一.Block介绍
二.Block截获变量
三.__block修饰符
四.Block的内存管理
五.Block的循环引用
一.Block介绍
1、什么是Block
- Block是将
函数
及其执行上下文
封装起来的对象。
2、Block的本质及其调用
- Block是一个
对象
,它封装了函数
和函数的执行上下文
。
MCBlock.m
- (void)method {
int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
Block(2);
}
源码解析
- 使用【
clang -rewrite-objc file.m
】编译后,可查看编译之后的.cpp
文件内容
MCBlock.cpp
// I表示当前类的实例方法,MCBlock当前类名,method当前方法名method
static void _I_MCBlock_method(MCBlock *self, SEL _cmd) {
int multiplier = 6;
/*
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
*/
int(*Block)(int) = (int((int (*)))&__MCBlock__method_block_impl_0((void *)__MCBlock__method_block_func_0, &__MCBlock__method_block_desc_0_DATA, multiplier));
/*
Block(2);
*/
((int (*)(__block_impl *, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, 2);
}
// __MCBlock__method_block_impl_0
struct __MCBlock__method_block_impl_0 {
struct __block_impl impl;
struct __MCBlock__method_block_desc_0* Desc;
int multiplier;
// 结构体构造函数
__MCBlock__method_block_impl_0(void *fp, struct __MCBlock__method_block_desc_0 *desc, int _mutiplier, int flags=0) : multiplier(_multiplier) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// __block_impl:Block结构体
struct __block_impl {
void *isa; // isa指针,Block是对象的标志
int Flags;
int Reserved;
void *FuncPtr; // 函数指针
};
Static int __MCBlock__method_block_func_0(struct __MCBlock__method_block_impl_0 *__cself, int num) {
int multiplier = __cself->multiplier; // bound by copy
return num * multiplier;
}
二.Block截获变量
-
局部变量:对于
基本数据类型
截获其值,对于对象类型
连同所有权修饰符
一起截获。 -
静态局部变量:以
指针形式
截获 -
全局变量:不截获
-
静态全局变量:不截获
1、截获变量源码解析
- 使用【
clang -rewrite-objc -fobjc-arc file.m
】命令编译
MCBlock.m
// 全局变量
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 对象类型> var %d", unsafe_obj);
NSLog(@"局部变量<__strong 对象类型> var %d", strong_obj;
NSLog(@"静态变量 %d", static_var);
NSLog(@"全局变量 %d", global_var);
NSLog(@"静态全局变量 %d", static_global_var);
};
Block();
}
MCBlock.cpp
int global_var = 4;
static int static_global_var = 5;
struct __MCBlock__method_block_impl_0 {
struct __block_impl impl;
struct __MCBlock_method_block_desc_0* Desc;
// 截获基本数据类型局部变量的值
int var;
// 连同所有权修饰符一起截获
__unsafe_unretained id unsafe_obj;
__strong id strong_obj;
// 以指针形式截获局部变量
int *static_var;
// 对全局变量和静态全局变量不截获
}
2、截获变量实例
- 基本数据类型变量
- (void)method {
int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d",Block(2));
}
结果:result is 12
- 静态局部变量
- (void)method {
static int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d",Block(2));
}
结果:result is 8
三.__block修饰符
1、什么场景下需要使用__block修饰符?
一般情况下,对被截获变量进行`赋值`操作需要添加`__block修饰符`
如下情况是否需要添加__block修饰符
?
示例1
{
NSMutableArray *array = [NSMutableArray array];
void(^Block)(void) = ^{
[array addObject: @123];
};
Block();
}
答:不需要。当前是对array变量的使用,而非赋值操作。
示例2
{
NSMutableArray *array = nil;
void(^Block)(void) = ^{
array = [NSMutableArray array];
};
Block();
}
答:当前是对array变量的赋值操作,所以需要在array的声明处添加__block修饰符,否则编译会报错。
2、对变量进行赋值
需要__block修饰符
:基本数据类型和对象类型的局部变量。不需要__block修饰符
:静态局部变量、全局变量、静态全局变量。
3、__block机制与原理
- __block修饰的变量变成了对象;
示例3
{
__block int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d",Block(2));
}
结果:result is 8
注意:block在栈上创建,__forwarding指针指向自己;堆上创建的block,__forwarding指针会指向其他地方。
四.Block的内存管理
- 栈Block:放在栈上
- 堆Block:放在堆上
- 全局Block:放在已初始化数据中
1、Block的Copy操作
1.1 栈上的Block
栈上Block的Copy
- 栈上有一个Block,Block中使用了__block变量,当对栈上的Block进行copy操作后,会在堆上产生和栈上一摸一样的堆Block和__block变量。随着变量作用域的结束,栈上的Block和__block变量都会随之销毁,而堆上的Block和__block变量仍然存在。
问题:在MRC环境下,当对栈上的Block进行Copy操作之后,是否会引起内存泄漏?
答:会。对栈上的Block进行Copy操作后,堆上的Block没有其他成员指向它,相当于alloc对象后,没有调用release效果相同。
栈上Block的销毁
- 栈上有一个__block变量和一个Block,在变量的作用域结束后或栈上的函数退出后,栈上的__block变量和Block都会被销毁。注意⚠️,由于__block修饰的变量已经成为对象,所以会被销毁。
1.2 栈上__block变量的Copy
- 对栈上__block变量进行Copy操作后,会在堆上产生一摸一样的__block变量,由于栈上__forwarding指针也指向堆上的__block变量,所以对__block变量的任何修改,最终都是对堆上__block变量的修改。
__forwarding指针存在的意义?
对栈上的__block变量进行Copy操作后,栈上的__forwarding指针指向堆上的__block变量,堆上的__forwarding指针也指向自身。
1.3 __forwarding的总结
typedef int(^Block)(int num);
@property (nonatomic, copy, readwrite) Block blk;
{
// 变成了对象multiplier
__block int multiplier = 10;
// Copy操作:则下面操作都是对堆上的multiplier进行操作
_blk = ^int(int num) {
return num * multiplier;
};
// multiplier对象的__forwarding指针对其成员变量multiplier赋值
multiplier = 6;
[self executeBlock];
}
- (void)executeBlock {
// 堆上的blk
int result = _blk(4);
NSLog(@"result is %d", result);
}
结果:result is 24
2、__forwarding存在的意义
- 不论在任何内存位置
- 都可以顺利的访问同一个__block变量
- 没有对__block变量进行Copy,操作的就是栈上的__block变量;如果进行了Copy操作,无论是在栈上还是堆上,对变量的修改或赋值操作都是堆上的__block变量,
五.Block的循环引用
1、如下代码会不会引起循环引用
{
_array = [NSMutableArray arrayWithObject: @"block"];
_strBlk = ^NSString*(NSString *num) {
return [NSString stringWithFormat:@"helloc_%@", array[0]];
};
_strBlk(@"hello");
}
- 会产生自循环方式循环引用。由于
当前对象
是通过Copy属性关键字声明的strBlk
,所以当前对象
对Block存在强引用
,而Block表达式中使用到当前对象
的array成员变量,根据Block截获变量部分内容
,Block对象中使用到对象类型的局部变量
,会连同其属性关键字一起被截获,array的属性关键字为strong
,所以在Block中有一个strong
类型的指针指向当前对象
,由此就产生了循环引用。
- 解决方案:在当前栈上声明(创建)一个
__weak
修饰的weakArray指针(变量),来指向原对象的array成员变量,在Block中使用weakArray,由此避免循环引用。 - 为什么通过使用__weak修饰符就可以避免循环引用?根据Block截获变量原理,对对象的截获是连同所有权修饰符一同截获,当被截获的对象是__weak修饰的对象时,在Block中产生的结构体中持有的截获对象也是__weak类型的。
{
_array = [NSMutableArray arrayWithObject: @"block"];
__weak NSArray *weakArray = _array;
_strBlk = ^NSString*(NSString *num) {
return [NSString stringWithFormat:@"helloc_%@", weakArray[0]];
};
_strBlk(@"hello");
}
2、如下代码有何问题
{
__block MCBlock *blockSelf = self;
_blk = ^int(int num) {
// var = 2
return num * blockSelf.var;
};
_blk(3);
}
- 在MRC下,不会产生循环引用。
- 在ARC下,会产生循环引用,引起内存泄漏。
- 解决方案:
{
__block MCBlock *blockSelf = self;
_blk = ^int(int num) {
// var = 2
int result = num * blockSelf.var;
blockSelf = nil;
return result;
};
_blk(3);
}
注意⚠️:如果没有调用此Block,则此循环引用环会一直存在!
本文总结
问题1:什么是Block?
Block是关于函数及其执行上下文封装的对象;
问题2:为什么Block会产生循环引用?
- 当前Block对当前对象的某一成员变量进行截获,Block会对对应变量有前引用,而当前对象对Block有前引用,就产生自循环引用。
声明成员变量为__weak变量来解决
! - 定义__block说明符也可能会引起循环引用。MRC下不会产生循环引用,ARC下会产生循环引用。解决方案:在Block中将__block修饰的变量置为nil,但是
注意:如果没有调用此Block,则此循环引用环会一直存在!
问题3:怎样理解Block截获变量的特性?
- 基本数据类型变量只截获其值
- 对象类型局部变量连同所有权修饰符一同被截获
- 对静态局部变量以
指针形式
截获 - 对全局变量和静态全局变量不截获
问题4:开发中遇到过哪些循环引用?如何解决?
见五.Block的循环引用
有任何问题,欢迎?各位评论指出!觉得博主写的还不错的麻烦点个赞喽?
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END