Block 的内部数据结构
不含变量的 Block
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^bananaBlock)(void) = ^{
NSLog(@"Block with nothing");
};
bananaBlock();
}
return 0;
}
输出:
2021-01-06 22:00:29.960525+0800 Block[66168:3680187] Block with nothing
实现下面指令生成c++,查看里面的数据结构:
clang -rewrite-objc main.m -o main.cpp
可以看到此时 Block 的数据结构是这样的:
struct __main_block_impl_0 {
struct __block_impl impl; // 包含具体信息
struct __main_block_desc_0* Desc; // 描述信息
// 可以看成是构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__block_impl 的结构:
struct __block_impl {
void *isa; // 指向类的 isa 指针
int Flags;
int Reserved;
void *FuncPtr; // 函数的实现地址
};
__main_block_desc_0 描述信息:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; // block 的内存大小
}
函数实现:
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_7__prlb26657hdbrtk1kmg7m8nw0000gn_T_main_b8fa28_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Block with nothing",18};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__prlb26657hdbrtk1kmg7m8nw0000gn_T_main_b8fa28_mi_0);
}
main 函数里面的代码大致是这样一个东西:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
// bananaBlock: 可以看成是一个对象
// __main_block_func_0: 是函数实现地址
// __main_block_desc_0_DATA: block描述信息
bananaBlock = __main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA);
// FuncPtr 具体的函数调用
bananaBlock->FuncPtr;
}
return 0;
}
从数据结构可以知道,block 其实就是一个 OC 对象,里面包含 isa 指针,函数调用地址,内存分配大小等信息(当然还可能包含捕获的变量,下面会演示)。
添加测试代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^bananaBlock)(void) = ^{
NSLog(@"Block with nothing");
};
bananaBlock();
NSLog(@"Block superclass type = %@", [[bananaBlock superclass] superclass]);
}
return 0;
}
输出:
2021-01-06 22:20:47.843630+0800 Block[66984:3696369] Block with nothing
2021-01-06 22:20:47.843886+0800 Block[66984:3696369] Block superclass type = NSObject
也是可以看出 Block 的最终父类是 NSObject
包含变量的 Block
测试代码:
// auto 全局变量
int orange = 6;
// static 全局变量
static int banana = 5;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 局部变量,自动类型:auto int milk = 7 (auto 默认不写)
int milk = 7;
// 局部变量, 静态类型
static int egg = 8;
void(^bananaBlock)(void) = ^{
NSLog(@"All info: orange = %d, banana = %d, milk = %d, egg = %d", orange, banana, milk, egg);
};
bananaBlock();
}
return 0;
}
例子中包含了 4 个变量,orange, banana, milk, egg。
输出:
2021-01-06 22:28:53.504792+0800 Block[67321:3702430] All info: orange = 6, banana = 5, milk = 7, egg = 8
转成 C++ 查看里面的数据结构
__main_block_impl_0 block 里面的数据结构是这样的:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int milk; // milk 是 auto int 类型的局部变量,它会直接拷贝到block的内存结构中,相当于值拷贝
int *egg; // egg 是 static int 静态类型局部变量,它会拷贝地址值到block的内存中,相当于引用拷贝(地址拷贝)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _milk, int *_egg, int flags=0) : milk(_milk), egg(_egg) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
main 函数的实现:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
int milk = 7;
static int egg = 8;
bananaBlock = __main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
milk,
&egg);
// FuncPtr 具体的函数调用
bananaBlock->FuncPtr;
}
return 0;
}
关系图:
函数调用:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int milk = __cself->milk; // bound by copy ... 从 block 取出来保存的值
int *egg = __cself->egg; // bound by copy ... 从block取出地址里面的值
// orange,banana 这两个全局变量直接访问
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__prlb26657hdbrtk1kmg7m8nw0000gn_T_main_a3862d_mi_0, orange, banana, milk, (*egg));
}
变量捕获类型
从上面捕获变量的例子中可以总结变量的捕获情况:
| 变量类型 | 是否捕获到block内部 | 访问方式 |
|---|---|---|
| 局部 auto 变量 | 是 | 值传递 |
| 局部 static 变量 | 是 | 地址传递 |
| 全局变量 | 否 | 直接访问 |
为什么会这样?其实可以这样想:
-
orange和banana这两全局变量始终会在内存的全局数据区里面,block始终能在需要的时候访到它们,如果把它们捕获到自己的内存结构中还要做额外的操作,所以这个没必要。 -
int milk是一个局部的自动变量,milk是一个局部变量,执行完当前方法,生命周期就没了。如果block不捕获它的话,以后想访问它就不行了,所以需要捕获,自己有一个拷贝(int milk 是基本数据类型)。 -
static int egg虽然是一个局部变量,但它是静态类型,它会一直呆在数据区,所以即使当前方法执行完毕,它还是会存在内存中,此时block只需要它的一个地址值就好了(毕竟 static int egg 以后可能还会修改)。
对 OC 对象的捕获情况
添加一个测试类 Cat:
@interface Cat : NSObject
// 包含一个 name 属性
@property (nonatomic, copy) NSString *name;
@end
@implementation Cat
@end
测试代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
cat.name = @"Tom";
void(^bananaBlock)(void) = ^{
NSLog(@"Hi %@", cat.name);
};
bananaBlock();
}
return 0;
}
输出:
2021-01-06 22:57:23.503163+0800 Block[68378:3721751] Hi Tom
查看里面 Block 定义的 __main_block_impl_0 的数据结构:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Cat *cat; // cat 是一个局部变量,以指针形式捕获进来了
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Cat *_cat, int flags=0) : cat(_cat) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
描述信息 __main_block_desc_0:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); // (*copy) 对象相关
void (*dispose)(struct __main_block_impl_0*); // (*dispose) 对象相关
}
可以看到在描述信息结构体中多了 (*copy) 和 (*dispose) 这两个东西,由于 block 此时捕获的是一个 OC 对象,所以此时需要执行 (*copy) 看是否需要对捕获对象的引用计数+1, (*dispose) 查看是否引用计数-1。
关系图:
Block 类型
在开发中使用的 Block 有3种类型:
-
NSGlobalBlock // 在全局数据区
-
NSMallocBlock // 在堆上
-
NSStackBlock // 在栈上
下面查看一下不同类型的区别:
在 MRC 环境调试
关闭 ARC
添加测试代码:
typedef void (^BananaBlock)(void);
typedef void (^AppleBlock)(void);
// 全局变量
int treeHeight = 100;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 局部变量 auto 变量。如果是 static int 那么它是一个静态类型变量,会存在全局数据区
int count = 2;
// 访问了局部变量 ... 为 __NSStackBlock__ 类型
BananaBlock bananaBlock = ^{
NSLog(@"bananaBlock count = %d, treeHeight = %d", count, treeHeight);
};
// 没访问局部变量 ... 为 __NSGlobalBlock__ 类型
AppleBlock appleBlock = ^{
NSLog(@"appleBlock treeHeight = %d", treeHeight);
};
// 访问了局部变量 ... 为 __NSStackBlock__ 类型
void(^orangeBlock)(void) = ^{
NSLog(@"orangeBlock count = %d", count);
};
// 访问了局部变量,并且添加了 copy 操作 ... 为 __NSMallocBlock__ 类型
void(^cherryBlock)(void) = [^{
NSLog(@"cherryBlock count = %d", count);
} copy];
// 什么变量都没访问 ... 为 __NSGlobalBlock__ 类型
void(^normalBlock)(void) = ^{
NSLog(@"Nothing");
};
NSLog(@"bananaBlock class = %@", [bananaBlock class]);
NSLog(@"appleBlock class = %@", [appleBlock class]);
NSLog(@"orangeBlock class = %@", [orangeBlock class]);
NSLog(@"cherryBlock class = %@", [cherryBlock class]);
NSLog(@"normalBlock class = %@", [normalBlock class]);
}
return 0;
}
输出:
2021-01-07 14:43:19.457406+0800 Block[83300:4678850] bananaBlock class = __NSStackBlock__
2021-01-07 14:43:19.457701+0800 Block[83300:4678850] appleBlock class = __NSGlobalBlock__
2021-01-07 14:43:19.457732+0800 Block[83300:4678850] orangeBlock class = __NSStackBlock__
2021-01-07 14:43:19.457758+0800 Block[83300:4678850] cherryBlock class = __NSMallocBlock__
2021-01-07 14:43:19.457794+0800 Block[83300:4678850] normalBlock class = __NSGlobalBlock__
在 ARC 环境调试
打开 ARC 开关
编译运行刚刚在 MRC 环境下添加的测试代码,输出如下:
2021-01-07 14:48:47.773190+0800 Block[83501:4682590] bananaBlock class = __NSMallocBlock__
2021-01-07 14:48:47.773701+0800 Block[83501:4682590] appleBlock class = __NSGlobalBlock__
2021-01-07 14:48:47.773763+0800 Block[83501:4682590] orangeBlock class = __NSMallocBlock__
2021-01-07 14:48:47.773783+0800 Block[83501:4682590] cherryBlock class = __NSMallocBlock__
2021-01-07 14:48:47.773805+0800 Block[83501:4682590] normalBlock class = __NSGlobalBlock__
例子中的 bananaBlock 和 orangeBlock 从 MRC 环境的 __NSStackBlock__ 变成了 ARC 环境下的 __NSMallocBlock__。
为什么会这样?主要是在 ARC 环境下,编译器自动为 bananaBlock 和 orangeBlock 添加了 copy 操作,于是它们就从内存中的栈上拷贝到堆了。
总结
| Block 类型 | 成立情况 |
|---|---|
| NSGlobalBlock | 没访问局部变量(auto 类型) |
| NSStackBlock | 访问了局部auto变量 |
| NSMallocBlock | NSStackBlock 执行 copy 操作 |
Block 的 copy 操作:
| Block 类型 | 执行 copy 操作以后 |
|---|---|
| NSGlobalBlock | 什么都不会发生 |
| NSStackBlock | 从栈拷贝到堆 |
| NSMallocBlock | 引用计数+1 |
对 OC 对象是 auto 变量的引用问题
如果 OC 对象是一个局部变量,那么有下面一些情况需要注意:
-
如果 Block 为 NSStackBlock, 那么这个 Block 不会对它访问的 OC 对象进行强引用;
-
如果 Block 为 NSMallocBlock,那么会根据传入的 OC 对象是的修饰符(__strong, __weak 或者 __unsafe_unretained )类型来进行是否强引用。
查看一下里面的捕获类型:
测试代码 1:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
cat.name = @"Tom";
void(^orangeBlock)(void) = ^{
NSLog(@"Hi %@", cat.name);
};
orangeBlock();
}
return 0;
}
使用如下指令查看一下 Block 的内部结构
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp (需要使用 runtime 特性,所以额外添加 -fobjc-arc -fobjc-runtime=ios-10.0.0,10.0.0 为随便指定的版本 )
Block 内部数据结构:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Cat *__strong cat; // cat 为 __strong 类型,强引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Cat *__strong _cat, int flags=0) : cat(_cat) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
此时从内部数据结构可以看出 Cat *__strong cat; 捕获的 cat 为 __strong 类型,强引用。
测试代码 2:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
cat.name = @"Tom";
// 添加 __weak 修饰
__weak Cat *weakCat = cat;
void(^orangeBlock)(void) = ^{
NSLog(@"Hi %@", weakCat.name);
};
orangeBlock();
}
return 0;
}
查看内部数据结构:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Cat *__weak weakCat; // cat 为 __weak 类型,弱引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Cat *__weak _weakCat, int flags=0) : weakCat(_weakCat) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以看出 Cat *__weak weakCat; 捕获的 cat 为 __weak 类型,弱引用。
也就是说:
-
如果传入block的是什么修饰符类型的 OC 对象,那么 block 就会捕获什么类型的修饰符对象。
-
如果 Block 是强引用 OC 对象,那么 OC 对象的引用计数会 +1,弱引用则不修改引用计数。
-
Block 被销毁的时候,会执行自己
__main_block_desc_0描述信息里面的static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); }void (*dispose)方法,并且如果有强引用 OC 对象,那么释放该 OC 对象,对象引用计数 -1。
OC 类中 Block 属性为什么使用 copy 修饰
如下所示:
typedef void(^ShowName)(void);
@interface Cat : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) ShowName show; // Block 属性, 使用 copy
@end
在 ARC 中,编译器会自动在下面情况将 NSStackBlock 拷贝到堆,成为 NSMallocBlock 延长生命周期:
-
block 作为
函数返回值 -
block 赋值给
__stong 指针的时候 -
block 作为
Cocoa API中方法名含有usingBlock的方法参数时 -
block 作为
GCD API的方法参数时
在 MRC 环境下,编译器在下面情况将 NSStackBlock 拷贝到堆,成为 NSMallocBlock:
- 主动为 block 执行 copy 操作
所以在 MRC 使用 block 作为 属性的时候需要如此声明:
@property (nonatomic, copy) MyBlock blockStuff; // 到时候 NSStackBlock -> NSMallocBlock
由于在 ARC 环境编译器会自动在满足条件的合适 NSStackBlock -> NSMallocBlock,所以 ARC 环境可以如下使用:
@property (nonatomic, copy) MyBlock blockStuff;
@property (nonatomic, strong) MyBlock blockStuff;
__block 修饰符
我们知道如果一个基本数据类型的自动变量被 block 访问了以后,它会使用值传递的形式传递进 block,然后block 有一个当前基本数据类型的值。所以如果该自动变量之后修改自己也是不会影响 block 里面捕获的值的,而且在 block 里面也不能修改自动变量的值。
如下代码编译器就会报错:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 5;
void(^orangeBlock)(void) = ^{
age = 6; // 编译不通过,报错
NSLog(@"Tom's age is %d", age);
};
age = 7; // 这里修改的值不会影响 block 里面的 age 值
orangeBlock();
}
return 0;
}
当然下面的例子就能对变量进行修改:
int height = 10; // 全局变量 block 不会捕获
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int age = 20; // static 类型的局部变量,存储在全局区中,拷贝到block的时候是地址拷贝
void(^orangeBlock)(void) = ^{
age = 30;
height = 20;
NSLog(@"age = %d, height = %d", age, height);
};
orangeBlock();
}
return 0;
}
但是这些全局变量或者静态变量它们也有一个缺陷,就是这些变量会一直存储在全局区中不会销毁,直到app结束。
使用 __block 修饰符修饰自动变量
使用 __block 修饰 age 变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 使用 __block 修饰
__block int age = 20;
void(^orangeBlock)(void) = ^{
age = 30; // 能修改,没问题
NSLog(@"age = %d", age);
};
orangeBlock();
}
return 0;
}
输出:
2021-01-07 17:02:04.422584+0800 Block[88456:4759058] age = 30
查看 __block 修饰符修饰自动变量后的数据结构
此时 __block int age 被封装成 __Block_byref_age_0 OC 对象:
struct __Block_byref_age_0 {
void *__isa; // 有 isa 指针,这是一个对象
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age; // 具体的变量
};
里面有 isa 指针。它们的关系图是这样的
所以现在 Block 不是直接拷贝 age 自动变量了,而是包含了一个 __Block_byref_age_0 对象。 它们的指向关系为:
__main_block_impl_0 -> __Block_byref_age_0 -> age
查看 __block 修饰 OC 对象
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 使用 __block 修饰 cat
__block Cat *cat = [[Cat alloc] init];
cat.name = @"Tom";
void(^orangeBlock)(void) = ^{
NSLog(@"age = %@", cat.name);
};
orangeBlock();
}
return 0;
}
__block Cat *cat 里面的数据结构是这样的:
struct __Block_byref_cat_0 {
void *__isa;
struct __Block_byref_cat_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Cat *__strong cat; // 强引用 cat,当然如果是这样声明 __block __weak Cat *cat = [[Cat alloc] init];, 那么就是 Cat *__weak cat
};
它们的关系图是这样的:
由于 cat 对象的声明是这样的: __block Cat,所以此时的引用关系为:
orangeBlock 强引用 __Block_byref_cat_0 强引用 cat 对象。
如果声明为:__block __weak Cat *cat = [[Cat alloc] init];,那么就是 __Block_byref_cat_0 弱引用 cat 对象,变成 Cat *__weak cat;
一个需要注意的事情:
-
在
ARC情况下:__block Cat *cat = [[Cat alloc] init];,__Block_byref_cat_0对cat对象是强引用 -
在
ARC情况下:__block __weak Cat *cat = [[Cat alloc] init];,__Block_byref_cat_0对cat对象是弱引用 -
在
MARC情况下:不管是__block Cat *cat, 还是__block __weak Cat *cat。__Block_byref_cat_0对cat对象都是弱引用
循环引用问题
形成强引用
添加测试 Cat 类:
typedef void(^ShowCatBlock)(void);
@interface Cat : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) ShowCatBlock catBlock;
@end
@implementation Cat
// 辅助打印,看 cat 对象是否销毁了
- (void)dealloc {
NSLog(@"Cat dealloc");
}
@end
测试代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
cat.name = @"Tom";
// show block 会强引用 cat, cat 拥有 show block,形成引用循环
cat.catBlock = ^{
NSLog(@"Hi %@", cat.name);
};
cat.catBlock();
NSLog(@"----- main func finish -----");
}
return 0;
}
输出:
2021-01-07 22:22:17.555665+0800 Block[86177:4092084] Hi Tom
2021-01-07 22:25:29.930663+0800 Block[86299:4094780] ----- main func finish -----
可以看到 main 函数执行完毕, cat 对象的 dealloc 方法也没有执行。为什么会这样?
是因为 cat 对象里面的 catBlock 数据结构是这样的:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Cat *__strong cat; // 对 cat 进行了强引用
};
catBlock 对 cat 对象进行了强引用。而由于 cat 对象本来就拥有 catBlock,所以也就对 catBlock 有强引用。
它们的关系从:
变成了这样:
导致形成引用循环,所以内存都得不到释放。
解决强引用
在 ARC 环境下:
- 使用
__weak修饰:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
cat.name = @"Tom";
// __weak 修饰,不会造成强引用
__weak typeof(cat) weakCat = cat;
cat.catBlock = ^{
NSLog(@"Hi %@", weakCat.name);
};
cat.catBlock();
NSLog(@"----- main func finish -----");
}
return 0;
}
输出:
2021-01-07 22:42:21.210631+0800 Block[86956:4106602] Hi Tom
2021-01-07 22:42:21.210914+0800 Block[86956:4106602] ----- main func finish -----
2021-01-07 22:42:21.210951+0800 Block[86956:4106602] Cat dealloc
cat 执行了 dealloc 方法,被释放了,没有和 catBlock 形成强引用。
catBlock 内部数据结构:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Cat *__weak weakCat; // __weak
};
- 使用
__unsafe_unretained修饰:
__unsafe_unretained typeof(cat) weakCat = cat;
测试的时候可以看到 cat 对象也是被释放了的。
catBlock 内部数据结构:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Cat *__unsafe_unretained weakCat; // __unsafe_unretained
};
__weak 和 __unsafe_unretained 修饰 cat 对象的区别:
-
都能打破 cat 对象和 catBlock 属性的引用循环
-
__weak 修饰的 cat 对象,在cat对象被销毁的时候,会自动设置为 nil
-
__unsafe_unretained 修饰的 cat 对象,在cat对象被销毁的时候,不会自动设置为 nil,还会保存之前存在的cat对象地址,如果此时访问 cat 对象,会报错
在 MRC 环境下:
- 使用
__unsafe_unretained修饰(原理和ARC 环境下一样)
@implementation Cat
// 辅助打印,看 cat 对象是否销毁了
- (void)dealloc {
[super dealloc]; // MRC 需要调用 super
NSLog(@"Cat dealloc");
}
@end
测试代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
cat.name = @"Tom";
__unsafe_unretained Cat* weakSelf = cat;
cat.catBlock = ^{
NSLog(@"Hi %@", weakSelf.name);
};
cat.catBlock();
[cat release]; // 需要 release cat
NSLog(@"----- main func finish -----");
}
return 0;
}
输出:
2021-01-07 23:09:54.680481+0800 Block[88453:4142948] Hi Tom
2021-01-07 23:09:54.680754+0800 Block[88453:4142948] Cat dealloc
2021-01-07 23:09:54.680785+0800 Block[88453:4142948] ----- main func finish -----
- 使用
__block修饰
测试代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
cat.name = @"Tom";
__block Cat* weakSelf = cat;
cat.catBlock = ^{
NSLog(@"Hi %@", weakSelf.name);
};
cat.catBlock();
[cat release]; // 需要 release cat
NSLog(@"----- main func finish -----");
}
return 0;
}
输出:
2021-01-07 23:10:23.464006+0800 Block[88479:4143845] Hi Tom
2021-01-07 23:10:23.464288+0800 Block[88479:4143845] Cat dealloc
2021-01-07 23:10:23.464318+0800 Block[88479:4143845] ----- main func finish -----
在 MRC 环境使用 __block 修饰也能打破引用循环是因为:
catBlock 强引用 __block 包装的对象, 而 __block 包装的对象对传入的 cat 对象不管什么情况都是弱引用。
(MRC 环境下不支持 __weak 修饰符)
注意一下的例子
修改了拷贝地址:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *name = @"Tom";
void(^orangeBlock)(void) = ^{
NSLog(@"Hi %@", name); // 拷贝了一个 name 的指针
};
name = @"Jerry"; // 修改了指向地址,所以对 block 没影响
orangeBlock();
}
return 0;
}
输出:
2021-01-07 16:29:45.255123+0800 Block[87301:4741637] Hi Tom
如果此时添加 __block 修饰:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSString *name = @"Tom";
void(^orangeBlock)(void) = ^{
NSLog(@"Hi %@", name); // 拷贝了一个 name 指针
};
name = @"Jerry"; // 多了一层引用,此时对 block 有影响
orangeBlock();
}
return 0;
}
输出如下:
2021-01-07 16:32:40.248197+0800 Block[87420:4743661] Hi Jerry
拷贝地址不变,只是修改了里面的属性内容:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Cat *cat = [[Cat alloc] init];
cat.name = @"Tom";
void(^orangeBlock)(void) = ^{
NSLog(@"Hi %@", cat.name); // 拷贝了一个 cat 的指针
};
cat.name = @"Jerry"; // 指向 cat 的地址还是一样
orangeBlock();
}
return 0;
}
输出:
2021-01-07 16:28:18.972028+0800 Block[87236:4740171] Hi Jerry
MRC环境的一个情况:
// 不是 auto 变量,是 static 类型, 输出: __NSGlobalBlock__
static int age = 2;
BananaBlock ageBlock = ^{
NSLog(@"ageBlock age = %d", age);
};
NSLog(@"ageBlock class = %@", [ageBlock class]);