有一个类 BlockClass
, 该类有一个类方法 +funcTest
#import "BlockClass.h"
@implementation BlockClass
+ (void) funcTest {
int a = 10;
NSLog(@"a1: %p, %d", &a,a);
void(^blockName)(void) = ^{
NSLog(@"block 内 a: %p, %d", &a,a);
};
a = 11;
NSLog(@"a2: %p, %d", &a,a);
blockName();
}
@end
执行 clang -rewrite-objc BlockClass.m
把 OC
代码转化成 c++
编译后的 funcTest 函数
static void _C_BlockClass_funcTest(Class self, SEL _cmd) {
// 声明临时变量 a
int a = 10;
// 打印变量 a 的地址, 值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f764b9_mi_0, &a,a);
// block 声明, 这里可以确定, block 的本质是一个结构体 __BlockClass__funcTest_block_impl_0
void(*blockName)(void) = ((void (*)())&__BlockClass__funcTest_block_impl_0((void *)__BlockClass__funcTest_block_func_0, &__BlockClass__funcTest_block_desc_0_DATA, a));
// 修改变量 a 的值
a = 11;
// 打印修改后的变量 a 的地址, 值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f764b9_mi_2, &a,a);
// 调用 block
((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
}
这就是 block 的真面目, 结构体!
- impl: 保存了 block 的基本信息
- Desc: block 的描述
- a: block 捕获的外部变量
struct __BlockClass__funcTest_block_impl_0 {
struct __block_impl impl;
struct __BlockClass__funcTest_block_desc_0* Desc;
int a;
/** 构造方法
注意这里, 把参数 _a 赋值给了成员变量 a, 这就是为什么外界修改了变量 a 的值, 调用block时打印的值不变, 因为 block 自己存了一份 a
*/
__BlockClass__funcTest_block_impl_0(void *fp, struct __BlockClass__funcTest_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block 的描述, 这里比较简单, 后面有复杂的案例
static struct __BlockClass__funcTest_block_desc_0 {
size_t reserved;
size_t Block_size;
} __BlockClass__funcTest_block_desc_0_DATA = { 0, sizeof(struct __BlockClass__funcTest_block_impl_0)};
// 保存了 block 的基本信息
struct __block_impl {
void *isa; // block 类型
void *FuncPtr; // 函数体指针
int Flags;
int Reserved;
};
// 就是 block 的 {}函数体
static void __BlockClass__funcTest_block_func_0(struct __BlockClass__funcTest_block_impl_0 *__cself) {
// 获取 a 的值: __cself 的成员变量 a, 不是外部 a 变量!
int a = __cself->a;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f619af_mi_0, &a,a);
}
打印结果也说明了 block 对外部变量的操作: 基本数据类型(值类型)赋值, 可以理解为深拷贝
Test1[43462:5694354] a1: 0x7ffee904c0dc, 10
Test1[43462:5694354] a2: 0x7ffee904c0dc, 11
Test1[43462:5694354] block 内 a: 0x600003de56d0, 10
局部静态变量
, 捕获的是它的指针修改变量 a
为静态变量
+ (void) funcTest {
static int a = 10;
NSLog(@"a1: %p, %d", &a,a);
void(^blockName)(void) = ^{
a++;
NSLog(@"block 内 a: %p, %d", &a,a);
};
a = 11;
NSLog(@"a2: %p, %d", &a,a);
blockName();
}
block 结构体捕获了静态变量 a 的地址
struct __BlockClass__funcTest_block_impl_0 {
struct __block_impl impl;
struct __BlockClass__funcTest_block_desc_0* Desc;
int *a;
__BlockClass__funcTest_block_impl_0(void *fp, struct __BlockClass__funcTest_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
Test1[43292:5690290] a1: 0x10b8b9748, 10
Test1[43292:5690290] a2: 0x10b8b9748, 11
Test1[43292:5690290] block 内 a: 0x10b8b9748, 12
全局变量
(包括全局静态变量), 不会捕获代码稍作修改, a 变成全局变量, block 内直接修改 a
int a = 10; // static int a = 10;
+ (void) funcTest {
NSLog(@"a1: %p, %d", &a,a);
void(^blockName)(void) = ^{
a ++;
NSLog(@"block 内 a: %p, %d", &a,a);
};
a = 11;
NSLog(@"a2: %p, %d", &a,a);
blockName();
}
执行 clang
编译成 c++, 我们发现, block 结构体里没有变量 a, 说明 block 没有捕获全局变量 a, 使用的时候直接访问全局变量
struct __BlockClass__funcTest_block_impl_0 {
struct __block_impl impl;
struct __BlockClass__funcTest_block_desc_0* Desc;
__BlockClass__funcTest_block_impl_0(void *fp, struct __BlockClass__funcTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
Test1[43415:5693256] a1: 0x105013748, 10
Test1[43415:5693256] a2: 0x105013748, 11
Test1[43415:5693256] block 内 a: 0x105013748, 12
__block
修饰符后, 就可以在 block
内修改外部变量的值总结: 被
__block
修饰后的变量 会被包装成__Block_byref_a_0
结构体, 通过结构体指针访问真正的变量.
我们把代码修改为如下, 仅给变量 a 添加 __block
修饰符
+ (void) funcTest {
__block int a = 10;
NSLog(@"a1: %p, %d", &a,a);
void(^blockName)(void) = ^{
a++;
NSLog(@"block 内 a: %p, %d", &a,a);
};
a = 11;
NSLog(@"a2: %p, %d", &a,a);
blockName();
}
同样, 执行 clang -rewrite-objc BlockClass.m
把 OC
代码转化成 c++
. 下面是与情景一
不同的地方
编译后的 funcTest 函数
__block
修饰变量 a
后, a
被包装成了结构体, 以指针
传递给 block
结构体, 引用类型赋值仅仅指针 copy
, 指向同一块内存, 浅拷贝.a
的时候其实是去找结构体 (__Block_byref_a_0 )a.__forwarding->a
, 真正的 a
.__Block_byref_a_0*
, 所以这时 block
内部就可以修改外部变量了.static void _C_BlockClass_funcTest(Class self, SEL _cmd) {
// 1. 关键!!! 创建了一个 __Block_byref_a_0对象 (不是指针哦), 第二个参数 __forwarding 保存的是它自己或被拷贝到堆上的地址(这里有点绕).
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
// 2. 注意!!! 后续对 a 的操作, 都是操作的结构体a.__forwarding->a
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f11dc2_mi_0, &(a.__forwarding->a),(a.__forwarding->a));
// 3. block 结构体第 3 个参数 a 是 __Block_byref_a_0 指针类型哦
void(*blockName)(void) = ((void (*)())&__BlockClass__funcTest_block_impl_0((void *)__BlockClass__funcTest_block_func_0, &__BlockClass__funcTest_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
// 修改真正的 a 的值
(a.__forwarding->a) = 11;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f11dc2_mi_2, &(a.__forwarding->a),(a.__forwarding->a));
((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
}
这就是 int a
转化后的结构体
struct __Block_byref_a_0 {
void *__isa;
// 保存了自己或被拷贝到堆上的内存地址
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
// 真正的 a
int a;
};
block
结构体有引用的外部变量 a
类型由int
=>__Block_byref_a_0*
, 指针
!!
struct __BlockClass__funcTest_block_impl_0 {
struct __block_impl impl;
struct __BlockClass__funcTest_block_desc_0* Desc;
__Block_byref_a_0 *a;
__BlockClass__funcTest_block_impl_0(void *fp, struct __BlockClass__funcTest_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
函数体
static void __BlockClass__funcTest_block_func_0(struct __BlockClass__funcTest_block_impl_0 *__cself) {
// 获取包装后的 a
__Block_byref_a_0 *a = __cself->a;
int b = 1;
// 操作 a: 拿到真正的a
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_2p60ltxs0zx3bhgp2cy935dr0000gn_T_BlockClass_f11dc2_mi_1, &(a->__forwarding->a),(a->__forwarding->a));
}
Desc 结构体也有改变, 多了两个函数 copy
和 dispose
.
在捕获变量为__block
修饰的基本类型 (会被转化为结构体对象),或者为对象时,block才会有这两个辅助函数。
static struct __BlockClass__funcTest_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __BlockClass__funcTest_block_impl_0*, struct __BlockClass__funcTest_block_impl_0*);
void (*dispose)(struct __BlockClass__funcTest_block_impl_0*);
} __BlockClass__funcTest_block_desc_0_DATA = { 0, sizeof(struct __BlockClass__funcTest_block_impl_0), __BlockClass__funcTest_block_copy_0, __BlockClass__funcTest_block_dispose_0};
static void __BlockClass__funcTest_block_copy_0(struct __BlockClass__funcTest_block_impl_0*dst, struct __BlockClass__funcTest_block_impl_0*src) {
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __BlockClass__funcTest_block_dispose_0(struct __BlockClass__funcTest_block_impl_0*src) {
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
_NSConcreteMallocBlock
是由_NSConcreteStackBlock
类型的 block 拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。
block 的拷贝最终都会调用_Block_copy_internal
函数,函数内判断是否调用 descriptor->copy
, 看上面代码 __BlockClass__funcTest_block_copy_0
内部调用了 _Block_object_assign
方法, 该方法根据变量的修饰符 (强引用: __strong
; 弱引用: __weak
、__unsafe_unretained
), 强引用或弱引用该变量.
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
...
aBlock = (struct Block_layout *)arg;
...
// Its a stack block. Make a copy.
if (!isGC) {
// 申请block的堆内存
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 拷贝栈中block到刚申请的堆内存中
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 改变isa指向_NSConcreteMallocBlock,即堆block类型
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
// 调用 Desc 的 copy 函数
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
else {
...
}
}
遗留问题:
打印结果让我很疑惑, 初始化的 a 的地址竟然与后面两个不一致? 看代码都是用的 结构体 a 的指针啊, 很疑惑.
Test1[43581:5697263] a1: 0x7ffee713b0d8, 10
Test1[43581:5697263] a2: 0x600000171ff8, 11
Test1[43581:5697263] block 内 a: 0x600000171ff8, 12
从上面的分析, 我们知道 block
也是一个对象,
当 block
被拷贝到堆上时,会调用 block
内部的 copy 方法, copy 方法内部会调用 _Block_object_assign
函数, 它会根据变量的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作.
__strong
修饰的对象被 block
捕获时, block
会强引用它.
可以使用 __weak
或 __unsafe_unretained
修饰 block 捕获的对象, 使 block
不强引用对象, 解决循环引用.
__weak
1.对象释放后会 weak 指针自动致为 nil;
2.对象会被注册到 autoreleasepool 中;
3.只能修饰对象,不能修饰基本数据类型, 只能在 ARC 模式下使用。
__unsafe_unretained
与 __weak
相比, 当对象被销毁时, __unsafe_unretained
修饰的指针不会被指向 nil
, 有野指针的风险
Block技巧与底层解析