本质
是将【函数】及其【执行上下文】封装起来的【对象】
分析
@implementation YXPerson
void test() {
int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
int res = Block(3);
NSLog(@"res => %d", res);
}
@end
使用 clang -rewrite-objc YXPerson.m
命令,生成 YXPerson.cpp
目标文件,然后打开 YXPerson.cpp
文件,找到 __test_block_impl_0
结构体。
- 结构体
__test_block_impl_0
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int multiplier; // 变量
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _multiplier, int flags=0) : multiplier(_multiplier) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 结构体
__block_impl
struct __block_impl {
void *isa; // 标记是一个对象
int Flags;
int Reserved;
void *FuncPtr; // 函数指针
};
定义块
int(*Block)(int) =
((int (*)(int))
&__test_block_impl_0(
(void *)__test_block_func_0,
&__test_block_desc_0_DATA,
multiplier)
);
- 函数
__test_block_func_0
static int __test_block_func_0(struct __test_block_impl_0 *__cself, int num) {
int multiplier = __cself->multiplier; // bound by copy
return num * multiplier;
}
截获变量
- 局部变量
- 基本数据类型【截获其值】
- 对象类型【截获所有权修饰符与值】
- 静态局部变量【指针形式】
- 全局变量【不截获】
- 静态全局变量【不截获】
分析
// 全局变量
int global_var = 4;
// 静态全局变量
static int static_global_var = 5;
void test() {
// 基本数据类型的局部变量
int var = 1;
// 对象类型的局部变量
__unsafe_unretained id unsafe_obj = nil;
__strong id strong_obj = nil;
// 静态局部变量
static int static_var = 3;
void(^Block)(void) = ^ {
NSLog(@"全局变量 %d", global_var);
NSLog(@"静态全局变量 %d", static_global_var);
NSLog(@"局部变量,基本数据类型 %d", var);
NSLog(@"局部变量,对象类型 %@", strong_obj);
NSLog(@"静态局部变量 %d", static_var);
};
Block();
}
使用 clang -rewrite-objc -fobjc-arc YXPerson.m
命令,生成文件。
// 全局变量
int global_var = 4;
// 静态全局变量
static int static_global_var = 5;
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
// 局部变量-基本数据类型
int var;
// 局部变量-对象类型
__strong id strong_obj;
// 静态局部变量
int *static_var;
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _var, __strong id _strong_obj, int *_static_var, int flags=0) : var(_var), strong_obj(_strong_obj), static_var(_static_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
案例
案例一
int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
int res = Block(3);
NSLog(@"res => %d", res);
局部变量,基本数据类型,以【值】的形式传递,所以输出 18
案例二
static int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
int res = Block(3);
NSLog(@"res => %d", res);
静态局部变量,以【指针】的形式传递,所以输出 12
__block
使用场景:对【截获变量】进行【赋值】操作时
以下截获变量修改需要使用 __block
:
- 局部变量
- 基本数据类型
- 对象类型
分析
__block int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
int res = Block(3);
NSLog(@"res => %d", res);
使用命令 clang -rewrite-objc -fobjc-arc YXPerson.m
生成文件
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
__Block_byref_multiplier_0 *multiplier; // by ref
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_multiplier_0 *_multiplier, int flags=0) : multiplier(_multiplier->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 结构体
__Block_byref_multiplier_0
struct __Block_byref_multiplier_0 {
void *__isa;
__Block_byref_multiplier_0 *__forwarding;
int __flags;
int __size;
int multiplier;
};
__block
修饰的变量变成了对象
__forwarding
指向 __block
变量
内存管理
-
__NSConcreteStackBlock
栈区,成员变量的的Block
如果使用 assgin,由于栈对应的函数释放掉后,对应栈也会释放掉,如果此时调用块会导致奔溃。 -
__NSConcreteMallocBlock
堆区 -
__NSConcreteGlobalBlock
全局
copy
的效果
-
__NSConcreteStackBlock
进行copy
拷贝到【堆】 -
__NSConcreteMallocBlock
进行copy
增加【引用计数】 -
__NSConcreteGlobalBlock
进行copy
什么也不做
栈上 Block
的销毁
变量作用域结束,对应栈上的__block
变量和Block
都会被释放掉
栈上 Block
的 copy
Block
执行 copy
操作之后的 Block
会导致内存泄露吗?
-
MRC
会 -
ARC
不会
栈上 __block
的 copy
操作
__forwardig
作用
经过了 copy
之后,无论出现在栈上的 __forwarding
,还是堆上的 __forwarding
,均指向堆上的 __block
变量
未经过 copy
,栈上的 __forwarding
指向栈上的 __block
变量
__forwarding
存在的意思
不论在任何内存位置,都可以顺利访问同一个__block
变量
案例
案例一
__block int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
int res = Block(3);
NSLog(@"res => %d", res);
由于是一个 __Block_byref_multiplier_0
对象,可以修改,所以输出 24
循环引用
案例一
如下代码是否存在循环引用?
typedef NSString *(^TestBlock)(NSString *str);
@interface YXPerson ()
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, copy) TestBlock testBlock;
@end
@implementation YXPerson
- (void)method {
self.array = [NSMutableArray array];
self.testBlock = ^NSString *(NSString *str) {
return self.array[0];
};
}
@end
环存在的原因:存在,自循环,对象持有一个 Block
,Block
又持有 self
解决方案:通过 weak
持有,修改为
- (void)method {
self.array = [NSMutableArray array];
__weak NSArray *weakArray = self.array;
self.testBlock = ^NSString *(NSString *str) {
return weakArray[0];
};
}
案例二:如下代码是否有问题?
- (void)method {
self.var = 3;
__block YXPerson *blockSelf = self;
self.testBlock = ^NSInteger(NSInteger num) {
return num * blockSelf.var;
};
NSLog(@"res %ld", (long)self.testBlock(2));
}
环存在的原因:对象持有 Block
,Block
持有 __block
变量,__block
持有对象
-
MRC
下不会有问题 -
ARC
产生循环引用,引起内存泄露
解决方案:断开 __block
持有对象这一环节。有一个弊端,块不调用,会一直存在循环引用。
- (void)method {
self.var = 3;
__block YXPerson *blockSelf = self;
self.testBlock = ^NSInteger(NSInteger num) {
NSInteger result = num * blockSelf.var;
blockSelf = nil;
return result;
};
NSLog(@"res %ld", (long)self.testBlock(2));
}