本文主要内容
一.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的循环引证
有任何问题,欢迎各位谈论指出!觉得博主写的还不错的麻烦点个赞喽