Block 是什么?
Block本质上也是一个oc对象,它内部也有一个isa指针。Block 是对一个函数指针及该函数调用所需的上下文环境的封装和实现。
1.1 Block 底层窥探
想看 Block 的底层,我们需要将 OC 代码编译成 C++代码去查看。
先创建项目来一段 OC 的代码作为例子:(代码环境为 MRC )
//项目环境为MRC
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block1)(void) = ^{
NSLog(@"Hello");
};
int a = 10;
void (^block2)(int) = ^(int x){
NSLog(@"Hello1 - %d",a); //a为值传递,block直接捕获a的值
};
__block int b = 20;
void (^block3)(int) = ^(int x){
b += x;
NSLog(@"Hello2 - %d",b); //b为指针传递,block直接捕获b的指针
};
block1();
block2(10);
block3(10);
NSLog(@"%@ %@ %@ %@", [block1 class], [block2 class],[block3 class], [[^{
NSLog(@"%d",a);
} copy]class]);
}
return 0;
}
把这个 main.m 文件编译成 C++ 文件:
1.打开终端进入 main.m 文件所在的文件夹
2.在终端输入一下代码:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m
成功之后会在同文件夹下看到 main.cpp 文件,这个文件就是编译过后的 C++ 文件。
那么 main 函数会被编译成:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
int a = 10;
void (*block2)(int) = ((void (*)(int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, a));
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
void (*block3)(int) = ((void (*)(int))&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, (__Block_byref_b_0 *)&b, 570425344));
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
((void (*)(__block_impl *, int))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2, 10);
((void (*)(__block_impl *, int))((__block_impl *)block3)->FuncPtr)((__block_impl *)block3, 10);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_n5hkyqj559d277b2twh255w80000gn_T_main_b954c6_mi_3, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block1, sel_registerName("class")), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block2, sel_registerName("class")),((Class (*)(id, SEL))(void *)objc_msgSend)((id)block3, sel_registerName("class")), ((Class (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_3((void *)__main_block_func_3, &__main_block_desc_3_DATA, a)), sel_registerName("copy")), sel_registerName("class")));
}
return 0;
}
可以看到 OC 基本上和 C++ 一一对应的。
先来看上述三个 Block 的定义:
block1:
void (block1)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
block2:
void (block2)(int) = ((void ()(int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, a));
block3:
void (block3)(int) = ((void ()(int))&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, (__Block_byref_b_0 *)&b, 570425344));
大致一看有一些共同点,我先分块标出然后一块一块看:
__main_block_impl_1() 结构体
这里的例子都拿 __main_block_impl_1 来解析,__main_block_impl_0和__main_block_impl_2都是一样,以此类推。
在 main.cpp 找到这个结构体的定义:
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
int a;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _a, int flags=0) : a(_a) {
//赋值
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_imp_1 结构体内有一个同名结构体函数 __main_block_imp_1,构造函数中对一些变量进行了 赋值 最终会返回一个结构体。
__main_block_impl_1 结构体内的 __main_block_impl_1()函数需要传入四个参数:
(void *)__main_block_func_1
&__main_block_desc_1_DATA
int _a
int flags=0) : a(_a)
其中,flags 有初始值,可以省略不传, a(_a)则表示传入的 _a 参数会自动赋值给 a 成员,相当于a = _a。
__main_block_impl_1()中的__block_impl结构体
__main__block_impl第一个变量就是 __block_impl 结构体,其结构为
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
可以看到 __block_impl 结构体内部就有一个 isa 指针,由此说明 block 本质上就是一个oc对象。
另外根据下面的代码
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _a, int flags=0) : a(_a) {
//赋值
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
__block_impl 结构体中 isa 指针被赋值为 _NSConcreteStackBlock 的地址,可以理解为其类对象地址,block就是 _NSConcreteStackBlock 类型的。
__main_block_func_1函数 封装了 block 的代码块,那么 FuncPtr 则存储着__main_block_func_1 函数的地址。
Desc 指向 __main_block_desc_1 结构体对象,其中存储__main_block_impl_1 结构体所占用的内存。
(void *)__main_block_func_1
cpp 文件中查看定义:
static void __main_block_func_1(struct __main_block_impl_1 *__cself, int x) {
int a = __cself->a; // 取出 block 中 a 的值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yd_n5hkyqj559d277b2twh255w80000gn_T_main_b954c6_mi_1,a);
}
在 __main_block_func_1 函数中首先 取出 block 中 a 的值,紧接着可以看到NSLog,可以发现这段代码恰恰是我们在block块中写下的代码。
那么 __main_block_func_1 函数中其实存储着我们block中写下的代码。而__main_block_impl_1 函数中传入的是 (void *)__main_block_func_1,也就说将我们写在block块中的代码封装成 __main_block_func_1 函数,并将 __main_block_func_1 函数的地址 传入了__main_block_impl_1 的构造函数中保存在结构体内(作为__main_block_func_1第一个参数使用)。
&__main_block_desc_1_DATA
cpp文件中查看定义:
static struct __main_block_desc_1 {
size_t reserved;
size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};
可以看出 __main_block_desc_1 中存储着两个参数:
size_t reserved
size_t Block_size //存储着 __main_block_impl_1 的占用空间大小
其中,reserved 赋值为0,Block_size存储着 __main_block_impl_1 的占用空间大小。最终将__main_block_desc_1 结构体的地址 传入 __main_block_func_1 中赋值给 desc(__main_block_func_1 的第二个参数)。
a 参数
参数 a 也就是我们定义的局部变量。
因为在 block 块中使用到 a 局部变量,所以在block声明的时候这里才会将 a 作为参数传入,也就说block会捕获局部变量 a, 如果没有在 block 中使用参数a ,这里将只会传入(void *)__main_block_func_1,&__main_block_desc_1_DATA 两个参数。比如上面的 block0的定义,只用到了两个参数__main_block_func_0 和 __main_block_desc_0_DATA。
block0:
(void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)))
1.2 block底层结构图
2. Block 类型
前面看源码的时候我们看到 block 的 isa 指针指向了_NSConcreteStackBlock 类对象地址:
impl.isa = &_NSConcreteStackBlock;
那么 block的类型就是_NSConcreteStackBlock 吗?
我们还拿最开始的例子,稍作修改:
NSLog(@"%@ %@ %@ %@ %@", [block1 class], [block2 class],[block3 class], [[^{
NSLog(@"%d",a);
} copy]class],[[block3 copy]class]);
输出为:
2021-04-01 23:37:11.158112+0800 blk[1547:42227] __NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__ __NSMallocBlock__ __NSMallocBlock__
通过输出看到block1的类型为:__NSGlobalBlock__ ,block2的类型为:__NSStackBlock__ ,block3的类型为:__NSStackBlock__ ,[block3 copy] 的类型为:__NSMallocBlock__ ,并不是 C++ 代码源码中 block 的isa指针指向的 _NSConcreteStackBlock 类型地址。我们可以猜测runtime运行时过程中也许对类型进行了转变。最终类型当然以runtime运行时类型也就是我们打印出的类型为准。
2.1 block的三种类型
autu变量:局部变量默认修饰为auto,因此auto关键字可以省略不写。autu变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时销毁。
局部变量中的auto变量与static变量是相对而言的。
下面在看一组代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1. 内部没有调用外部变量的block __NSGlobalBlock__
void (^block1)(void) = ^{
NSLog(@"block1");
};
// 2. 内部调用外部auto变量的block __NSStackBlock__
int a = 10;
void (^block2)(int) = ^(int x){
NSLog(@"block2 - %d",a); //a为值传递,block直接捕获a的值
};
// 3. 内部调用外部static变量的block __NSGlobalBlock__
static int m = 30;
void (^block3)(void) = ^{
NSLog(@"block3 - %d",m); //m为指针传递,block直接捕获m的指针
};
block1();
block2(10);
block3();
// 3. 调用block 的copy __NSMallocBlock__
NSLog(@"block1类型%@ \n\nblock2类型%@ \n\nblock3类型%@ \n\nblock直接copy类型%@ \n\n[block1 copy]类型%@ \n\n[block2 copy]类型%@ \n\n[block3 copy]类型%@", [block1 class], [block2 class],[block3 class], [[^{
NSLog(@"%d",a);
} copy]class],[[block1 copy]class],[[block2 copy] class],[[block3 copy] class]);
}
return 0;
}
控制台输出为:
block1类型__NSGlobalBlock__
block2类型__NSStackBlock__
block3类型__NSGlobalBlock__
block直接copy类型__NSMallocBlock__
[block1 copy]类型__NSGlobalBlock__
[block2 copy]类型__NSMallocBlock__
[block3 copy]类型__NSGlobalBlock__
那么可以归纳为以下三类情况:
| block类型 | 环境 | 存储区域 |
|---|---|---|
__NSGlobalBlock__ | 没有访问auto变量或者访问了static变量 | 数据段(已经初始化的全局变量、静态变量,程序运行结束时回收) |
__NSStackBlock__ | 访问了auto变量 | 栈区 |
__NSMallocBlock__ | __NSStackBlock__调用copy后 | 堆区 |
另外:
__NSGlobalBlock__ 类型的block 在调用了 copy以后,还是__NSGlobalBlock__ 类型。
2.2 对 block 使用 copy
先看一个例子:
//整个项目处于MRC环境
#import <Foundation/Foundation.h>
void (^testBlock)(void);
void creatABlock() {
//创建一个 __NSStackBlock__
int a = 10;
testBlock = ^{
NSLog(@"a == %d",a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
creatABlock();
testBlock();
}
return 0;
}
程序输出:
2021-04-02 11:26:05.272599+0800 blk[2318:68865] a == -272632568
输出的并不是 a == 10,为什么会输出 a == -272632568?
因为上述代码中创建的block是 __NSStackBlock__ 类型的,因此block是存储在栈中的,那么当 creatABlock() 函数执行完毕之后,栈内存中block所占用的内存已经被系统回收,但是当通过 testBlock() 去执行 block 的时候,由于block所占用的内存已经被系统回收,所以有可能出现错乱的数据。
那么应该如何避免这种情况?
可以通过 copy 将 __NSStackBlock__ 类型的block 转化 为 __NSMallocBlock__ 类型的block,将block存储在堆中,以下是修改后的代码。
//整个项目处于MRC环境
#import <Foundation/Foundation.h>
void (^testBlock)(void);
void creatABlock() {
//创建一个 __NSStackBlock__ 并通过 copy 使其转换成 __NSMallocBlock 类型
int a = 10;
testBlock = [^{
NSLog(@"a == %d",a);
} copy];
[testBlock release];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
creatABlock();
testBlock();
}
return 0;
}
控制台输出:
2021-04-02 11:33:48.081374+0800 blk[2410:72797] a == 10
在平时开发过程中MRC环境下经常需要使用copy来保存block,将栈上的block拷贝到堆中,即使栈上的block被销毁,堆上的block也不会被销毁,需要我们自己调用release操作来销毁。而在ARC环境下系统会自动调用copy操作,使block不会被销毁,总之,对 block 调用 copy 操作,目的就是使得这样在作用域外调用该block程序不出现问题。在ARC下对Block使用关键字 copy 是MRC 保留下来的一个传统,在ARC下,使用 strong 和 copy 都可以。
ARC环境下自动将block进行一次copy操作情形
1. block作为Cocoa API中方法名含有usingBlock的方法参数时:
NSArray *array = @[@1,@4,@5];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// code
}];
2. block作为GCD API的方法参数时
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//code to be executed after a specified delay
});
3. block作为函数返回值时
//整个项目处于ARC环境
#import <Foundation/Foundation.h>
typedef void (^TestBlock)(void);
TestBlock creatABlock() {
//创建一个 __NSStackBlock__
int a = 10;
TestBlock block = ^{
NSLog(@"a == %d",a);
};
return block;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestBlock block2 = creatABlock();
block2();
}
return 0;
}
3.Block 变量的捕获
再来看一段代码:
/整个项目处于MRC环境
#import <Foundation/Foundation.h>
int c = 30; //全局变量
static int d = 40;//全局静态变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10; //局部auto 变量
static int b = 20; //局部static静态变量
void(^block1)(void) = ^void {
NSLog(@"block1 a = %d",a);
};
void(^block2)(void) = ^void(void) {
NSLog(@"block2 b = %d",b);
};
void(^block3)(void) = ^void(void) {
NSLog(@"block2 a + b = %d",a + b);
};
void(^block4)(void) = ^void(void) {
NSLog(@"block3 c = %d",c);
};
void(^block5)(void) = ^void(void) {
NSLog(@"block4 d = %d",d);
};
block1();
block2();
block3();
block4();
block5();
}
return 0;
}
控制台输出:
2021-04-02 13:20:30.659076+0800 blk[3651:121229] block1 a = 10
2021-04-02 13:20:30.659532+0800 blk[3651:121229] block2 b = 20
2021-04-02 13:20:30.659675+0800 blk[3651:121229] block2 a + b = 30
2021-04-02 13:20:30.659721+0800 blk[3651:121229] block3 c = 30
2021-04-02 13:20:30.659754+0800 blk[3651:121229] block4 d = 40
同样的我们编译成 cpp 文件可以看到:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
static int b = 20;
void(*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); //直接捕获a的值
void(*block2)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, &b));//捕获 b 的地址
void(*block3)(void) = ((void (*)())&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, a, &b)); //捕获a的值和b的地址
void(*block4)(void) = ((void (*)())&__main_block_impl_3((void *)__main_block_func_3, &__main_block_desc_3_DATA)); //没有捕获任何值
void(*block5)(void) = ((void (*)())&__main_block_impl_4((void *)__main_block_func_4, &__main_block_desc_4_DATA)); ////没有捕获任何值
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
((void (*)(__block_impl *))((__block_impl *)block3)->FuncPtr)((__block_impl *)block3);
((void (*)(__block_impl *))((__block_impl *)block4)->FuncPtr)((__block_impl *)block4);
((void (*)(__block_impl *))((__block_impl *)block5)->FuncPtr)((__block_impl *)block5);
}
return 0;
}
局部 auto 变量
自动变量a会被捕获到 block 内部,也就是说 block 内部会专门新增加一个参数来存储变量a 的值。 auto只存在于局部变量中,访问方式为 值传递,通过上述对 a 参数的解释我们也可以确定确实是值传递。
如果我们在block1()调用之前对a进行赋值,例如:
...
a = 20;
block1();
...
输出a的值还是10,因为auto 变量a的值被 block1直接捕获,捕获a的值,因此无法再后面再获取a新赋的值。
注:
冒号后面跟的是赋值,这种写法是C++的特性。
A( int aa, int bb ):a(aa),b(bb) {
}
相当于
A( int aa, int bb ) {
a=aa;
b=bb;
}
局部 static 变量
static修饰的局部变量,在程序生命周期内不会被销毁,通过上面的图可以看出 b 传递的是*b 也就是说直接通过 值传递 把内存地址传进去进行修改了。当然我们如果在block2调用之前对 b = 50 进行修改,由于b的地址变动,内部存储的值发生了改变,那么由于block2捕获的是b的地址,自然获取到的b的值是改动后的值也就是 b = 50。
block3再次印证了 auto变量是值传递,static 变量是指针传递。
如果我们在block2()调用之前对b进行赋值,例如:
...
b = 30;
block2();
...
输出 b 的值会是30,因为 static 变量 b 的值通过指针(地址)传递的方式被 block2 捕获,因此虽然对 b 赋予了新值,但是由于 b 的地址未变,所以会获取到 b 的新值。
全局变量 (无论是不是static)
从 cpp 文件可以看出来,并没有捕获全局变量c和d,访问的时候,是直接去访问的,根本不需要捕获。
随时修改 c 和 d 的值后调用 block4() 和 block5(),block都会输出最新的值。
总结
| 变量类型 | 是否捕获到block内部 | 访问方式 |
|---|---|---|
| auto变量 | 是 | 值传递 |
| static变量 | 是 | 指针传递 |
| 全局 | 否 | 直接访问 |
4. 如何修改变量
使用__block 修改局部变量
先来看一段代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int a = 10;
void(^block)(void) = ^void(void) {
a = 20;
NSLog(@"a = %d",a);
};
}
return 0;
}
这样写的结果是直接报错,提示Variable is not assignable (missing __block type specifier) ---- 变量不可分配。
提示用 __block 来修饰auto变量,那么修改代码为:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block auto int a = 10;
void(^block)(void) = ^void(void) {
a = 20;
NSLog(@"a = %d",a);
};
block();
}
return 0;
}
输出为:
2021-04-02 16:19:21.547753+0800 blk[5683:205901] a = 20
可以看到变量 a 被重新修改为 20 。
static修改局部变量为静态局部变量
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int a = 10;
void(^block)(void) = ^void(void) {
a = 20;
NSLog(@"a = %d",a);
};
block();
}
return 0;
}
输出为:
2021-04-02 16:34:25.757481+0800 blk[5907:214686] a = 20
使用全局变量
#import <Foundation/Foundation.h>
int a = 10;//使用全局变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^void(void) {
a = 20;
NSLog(@"a = %d",a);
};
block();
}
return 0;
}
输出为:
2021-04-02 16:35:31.515647+0800 blk[5930:215580] a = 20
5.循环引用
将环境切换为ARC,创建一个 Person类:
Person.h 文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property(nonatomic, copy) void (^testBlock)(void);
@property (assign, nonatomic) int age;
@end
Person.m 文件
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
main.h 文件
#import <Foundation/Foundation.h>
#import "Person.h"
int a = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
person.testBlock = ^{
NSLog(@"person.age--- %d",weakPerson.age);
};
}
return 0;
}
调用发现程序运行时,Person 的 dealloc 方法并没有执行,说明 person 在大括号结束之后,person依然没有被释放,产生了循环引用。
为什么会产生循环引用?
因为在 Person类中testBlock 是其属性,通过 copy 持有,而 testBlock内部有个强指针指向person,person 和 testBlock互相持有,造成了循环引用。
将上述代码通过
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
编译为 cpp 文件可见:
那么怎样能解除循环引用呢?
5.1 使用 __weak 解决循环引用
我们将上面的代码稍作修改为:
nt main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *weakPerson = person;
person.testBlock = ^{
NSLog(@"person.age--- %d",weakPerson.age);
};
}
return 0;
}
继续编译为 cpp 文件为:
程序运行控制台输出:
2021-04-03 00:54:38.432005+0800 blk[2928:80464] -[Person dealloc]
可以看出通过 __weak 打破了循环引用,Person成功释放,调用了 dealloc。
5.2 其他可以解决循环引用的方法还有:
1、__unsafe_unretained解决循环引用 :
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
__unsafe_unretained Person *weakPerson = person;
person.testBlock = ^{
NSLog(@"person.age--- %d",weakPerson.age);
};
NSLog(@"--------");
}
return 0;
}
虽然__unsafe_unretained可以解决循环引用,但是最好不要用,因为
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变。
2、__block解决循环引用 :
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
person.age = 10;
person.testBlock = ^{
NSLog(@"person.age--- %d",person.age);
//这一句不能少
person = nil;
};
// 必须调用一次
person.block();
NSLog(@"--------");
}
return 0;
}
上面的代码中,也是可以解决循环引用的。但是需要注意的是,person.block();必须调用一次,为了执行person = nil;
5.3 __weak 和 __strong
__weak typeof(self) weakSelf = self;
person.block = ^{
__strong typeof(weakSelf) myself = weakSelf;
NSLog(@"age is %d", myself->_age);
};
在 block 内部重新使用 __strong 修饰 self 变量 是为了在 block 内部有一个强指针指向 weakSelf 避免在 block 调用的时候 weakSelf 已经被销毁。