什么是block
带有自动变量(局部变量)的匿名函数。
无外部变量访问时Block的底层结构
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^test)(void) = ^{
NSLog(@"Block");
};
test();
}
return 0;
}
- 通过xcrun指令将main.m文件转换成C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
- 查看生成的main.cpp文件,首先看main函数,转换成C++之后,结构如下,此处去除了多余的强转操作,方便阅读
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//Block的定义
void(*test)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
//block的调用
test->FuncPtr(test);
}
return 0;
}
- Block在编译完成之后,转换成了__main_block_impl_0类型的结构体,它的内部结构如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//此处就是调用的NSLog
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_main_4f0065_mi_0);
}
struct __main_block_impl_0 {
//存放了block的一些基本信息,包括isa,函数地址等等
struct __block_impl impl;
//存放block的一些描述信息
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是直接存放在__main_block_impl_0结构体的内部,所以__main_block_impl_0结构体也可以转换成如下形式
struct __block_impl {
void *isa; //isa指针,可以看出Block其实就是一个OC对象
int Flags; //标识,默认为0
int Reserved; //保留字段
void *FuncPtr;//函数内存地址
};
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
};
block将我们所要调用的代码封装成了函数__main_block_func_0,并且将函数__main_block_func_0的内存地址保存在到void *FuncPtr中,具体函数如下
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//此处就是调用的NSLog
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_main_4f0065_mi_0);
}
结构体__main_block_desc_0中则保存了block所占用内存大小等描述信息
static struct __main_block_desc_0 {
size_t reserved; //保留字段
size_t Block_size; //__main_block_impl_0结构体所占内存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
访问外部变量时Block的底层结构
我们在使用Block的过程中,可以在Block内部访问外部的变量,包含局部变量、静态变量(相当于私有的全局变量)、全局变量等等。现在就通过一个Demo来看一下block底层是如何访问外部变量的。
- 首先创建Demo,在main.m文件中添加如下代码
//定义全局变量c
int c = 30;
int main(int argc, const char * argv[]) {
@autoreleasepool {
//局部变量a
int a = 10;
//静态变量b
static int b = 20;
void(^test)(void) = ^{
NSLog(@"Block - %d, %d, %d", a, b, c);
};
test();
}
return 0;
}
- 将main.m转换成C++代码后,再次查看main函数,结果如下
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
static int b = 20;
void(*test)(void) = (&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
a,
&b));
test->FuncPtr(test);
}
return 0;
}
可以看出,此时__main_block_impl_0结构体中多了两个参数,分别是局部变量a的值,静态变量b的指针,也就是它的内存地址。
- 查看__main_block_impl_0结构体的内存结构
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
int *b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
发现,在__main_block_impl_0结构体中多了两个成员变量,一个是int a,一个是int *b
- 当通过test->FuncPtr(test)执行block时,会通过结构体中的FuncPtr找到函数__main_block_func_0的地址进行调用,查看__main_block_func_0函数如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_main_064cd6_mi_0, a, (*b), c);
}
在__main_block_func_0函数中,访问局部变量a和静态变量b时都是通过传递过来的__main_block_impl_0结构体拿到对应的成员变量进行访问,但是全局变量c并没有存放在结构体中,而是直接进行访问。
结论,block中有变量捕获的机制
由此我们就可以得出结论,block中有变量捕获的机制
- 当访问局部变量的时候,会将局部变量的值捕获到block中,存放在一个同名的成员变量中。
- 当访问静态变量时,会将静态变量的地址捕获到block中,存放在一个同名的成员变量中
- 当访问全局变量时,因为全局变量是一直存在,不会销毁,所以在block中直接访问全局变量,不需要进行捕获
此处需要注意的是,其实在OC中有个默认的关键字auto,在我们创建局部变量的时候,会默认在局部变量前加上auto关键字进行修饰,例如上文中的int a,其实就相当于auto int a。auto关键字的含义就是它所修饰的变量会自动释放,也表示着它所修饰的变量会存放到栈空间,系统会自动对其进行释放。
block总结
block底层结构总结
block在编译完成之后会转换成结构体进行保存,结构体中的成员变量如下,其中在成员变量descriptor指向的结构体中,多了两个函数指针分别为copy和dispose,这两个函数和block内部对象的内存管理有关,后面会具体说明。block底层结构总结
block使用变量捕获机制来保证在block内部能够正常的访问外部变量。
block变量捕获总结
- 当block访问的是auto类型的局部变量时,会将局部变量捕获到block内部的结构体中,并且是直接捕获变量的值。
- 当block访问的是static类型的静态变量时,会将静态变量捕获到block内部的结构体中,并且捕获的是静态变量的地址。
- 当block访问的是全局变量时,不会进行捕获,直接进行访问。
block的类型
在OC当中block其实拥有三种类型(共6种,另外3中系统生成不常见),可以通过class或者isa指针来查看block具体的类型。
int main(int argc, const char * argv[]) {
@autoreleasepool {
//第一种类型NSGlobalBlock
NSLog(@"%@",[^{
NSLog(@"NSGlobalBlock");
} class]);
//第二种类型NSStackBlock
int a = 10;
NSLog(@"%@",[^{
NSLog(@"%d", a);
} class]);
//第三种类型NSMallocBlock - 1
void(^test2)(void) = ^{
NSLog(@"NSMallocBlock - %d", a);
};
NSLog(@"%@",[test2 class]);
//第三种类型NSMallocBlock - 2
NSLog(@"%@",[[^{
NSLog(@"%d", a);
} copy] class]);
}
return 0;
}
运行结果如下:
运行后可以发现,block可以有三种类型,分别是NSGlobalBlock、NSStackBlock和NSMallocBlock,这三种类型分别存放在.data区、栈区和堆区。对应的结构图如下
图中的block类型和上文中打印出来的block类型对应关系如下class方法返回类型 | isa指向类型 |
---|---|
NSGlobalBlock | _NSConcreteGlobalBlock |
NSStackBlock | _NSConcreteStackBlock |
NSMallocBlock | _NSConcreteMallocBlock |
但是不管它是哪种block类型,最终都是继承自NSBlock类型,而NSBlock继承自NSObject,所以这也说明了block本身就是一个对象。
如何区分block类型
在上述示例中,提到了四种生成不同类型的block的方法,分别如下:
- 没有访问局部变量的block,并且没有强指针指向block,则此block为NSGlobalBlock
- 访问了局部变量的block,但是没有强指针指向block,则此block为NSStackBlock
- 访问了局部变量的block,并且有强指针指向block,则此block为NSMallocBlock
- NSStackBlock类型的block,执行了copy操作之后,生成的block为NSMallocBlock
其实第三点和第四点生成的都是NSMallocBlock,由此我们就可以得到下面的结论
block的类型 | block执行的操作 |
---|---|
NSGlobalBlock | 没有访问auto类型的变量 |
NSStackBlock | 访问了auto类型的变量 |
NSMallocBlock | __NSStackBlock__类型的block执行了copy操作 |
block执行copy操作后的内存变化
NSGlobalBlock、NSStackBlock和NSMallocBlock三种类型的block分别存放在了数据区、栈区和堆区。将三种类型的block分别进行copy操作之后,产生的结果如下:
ARC环境下哪些操作会自动进行copy操作?
在上述示例中,NSStackBlock类型的block,执行了copy操作之后,生成的block为NSMallocBlock,其实不止这一种方式生成NSMallocBlock,以下是OC中在ARC环境下自动触发copy操作的几种情况:
- block作为返回值时,会自动进行copy
typedef void(^block)(void);
block test(){
return ^{
NSLog(@"NSMallocBlock");
};
}
- 使用__strong类型的指针指向block时,会执行copy操作
void(^test2)(void) = ^{
NSLog(@"NSMallocBlock - %d", a);
};
- block作为Cocoa API中含有usingBlock的方法的参数时,会执行copy操作
NSArray *arr = @[@"1",@"2"];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"NSMallocBlock");
}];
- block作为GCD方法的参数时会执行copy操作
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"NSMallocBlock");
})
我们平常在使用block作为属性的时候,都会使用copy修饰符来修饰(用强指针也可以),其实内部就是对block进行了一次copy操作,将block拷贝到堆上,以便我们手动管理block的内存
blocK 访问对象类型
Block访问的外部变量都是基本数据类型,所以不涉及到内存管理,如果在block中访问外部对象时,block内部又是什么样的结构呢?
int main(int argc, const char * argv[]) {
@autoreleasepool {
//默认对象
NSObject *obj1 = [[NSObject alloc] init];
void(^test1)(void) = ^{
NSLog(@"NSMallocBlock - %@", obj1);
};
test1();
//使用__weak指针修饰对象
NSObject *obj2 = [[NSObject alloc] init];
__weak typeof(obj2) weakObj = obj2;
void(^test2)(void) = ^{
NSLog(@"NSMallocBlock - %@", weakObj);
};
test2();
}
return 0;
}
- 使用如下指令将main.m文件转换成C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
此处由于使用了__weak关键字来修饰对象,涉及到runtime,所有需要指定runtime的版本。
- 转换成main.cpp文件后,查看block的底层结构为
//直接访问外部对象的block内部结构
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//生成strong类型的指针
NSObject *__strong obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//访问__weak修饰符修饰的外部对象的block内部结构
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
//自动生成weak类型的指针
NSObject *__weak weakObj;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, NSObject *__weak _weakObj, int flags=0) : weakObj(_weakObj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
这时发现,如果直接在block中访问外部的auto类型的对象,默认是在block结构体中生成一个strong类型的指针指向外部对象,如结构体__main_block_impl_0 。如果在block中访问了__weak修饰符修饰的外部对象,那么在它的内部会生成一个weak类型的指针指向外部对象,如结构体__main_block_impl_1。
在__main_block_impl_0的构造函数中,obj(_obj)就代表着,以后构造函数传过来的_obj参数会自动赋值给结构体中的成员变量obj。
- 由于__main_block_desc_0和__main_block_desc_1结构相同,所以以下只以__main_block_desc_0为例,查看__main_block_desc_0结构体,会发现它内部新增加了两个函数指针,如下
static struct __main_block_desc_0 {
size_t reserved; //保留字段
size_t Block_size; //整个block所占内存空间
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); //copy函数
void (*dispose)(struct __main_block_impl_0*); //dispose函数
} __main_block_desc_0_DATA = { 0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0};
新增加了copy和dispose两个函数指针,对应着函数__main_block_copy_0和__main_block_dispose_0,如下
//copy指针指向的函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
//dispose指针指向的函数
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
之前说过,block封装了函数调用和函数调用环境,这也就意味这如果它引用了外部的对象,就需要对外部对象进行内存管理操作。__main_block_copy_0函数内部会调用_Block_object_assign函数,它的主要作用是根据外部引用的对象的修饰符来进行相应的操作,如果外部对象是使用__strong来修饰,那么_Block_object_assign函数会对此对象进行一次类似retain的操作,使得外部对象的引用计数+1。
__main_block_dispose_0函数内部会调用_Block_object_dispose函数,它的作用就是在block内部函数执行完成之后对block内部引用的外部对象进行一次release操作
block被copy堆上copy和dispose函数调用时机
函数 | 调用时机 |
---|---|
copy | 栈上的block被复制到堆上 |
dispose | 堆上的block被释放时 |
__block的用法
让我们在block内部来修改外部变量的值,当然,block只能用来修饰auto变量,不能用来修饰全局变量和静态变量。
typedef void(^TestBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block XLPerson *person = [[XLPerson alloc] init];
__block int a = 10;
TestBlock block = ^{
person = nil;
a = 20;
NSLog(@"block -- a:%d, person:%@",a,person);
};
block();
NSLog(@"block调用后,a:%d, person:%@",a,person);
}
return 0;
}
通过以下指令转换成C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
- 首先查看block结构体的源码,发现block内部多了两个指针,__Block_byref_person_0类型的指针person和__Block_byref_a_1类型的指针a
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__Block_byref_a_1 *a; // by ref
};
- 再查看main函数中,局部变量和block的创建方式
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//封装person对象
__Block_byref_person_0 person = {
0, //
&person,
33554432,
sizeof(__Block_byref_person_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
objc_msgSend(objc_msgSend(objc_getClass("XLPerson"), sel_registerName("alloc")), sel_registerName("init"))};
//封装变量a
__Block_byref_a_1 a = {
0,
(__Block_byref_a_1 *)&a,
0,
sizeof(__Block_byref_a_1),
10
};
//创建block
TestBlock block = (&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
&person,
&a,
570425344));
block->FuncPtr(block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_f3_lg91hwts5rjdlzjph0sn82m80000gp_T_main_115560_mi_1,(a.__forwarding->a),(person.__forwarding->person));
}
return 0;
}
__block修饰对象类型auto变量
通过__block修饰的person对象在编译后被封装成了__Block_byref_person_0类型的结构体,内部有多个成员变量,如下
#将person对象封装成结构体__Block_byref_person_0
struct __Block_byref_person_0 {
void *__isa; //isa指针
__Block_byref_person_0 *__forwarding; //forwarding指针
int __flags; //标识位
int __size; //结构体所占内存大小
void (*__Block_byref_id_object_copy)(void*, void*);//函数指针指向copy函数
void (*__Block_byref_id_object_dispose)(void*); //函数指针指向dispose函数
XLPerson *__strong person; //强引用XLPerson的实例对象
};
//__Block_byref_person_0结构体的创建与赋值
__Block_byref_person_0 person = {
0, //对应isa指针,传0
&person, //对应forwarding指针,将结构体自身的地址传给了forwarding指针
33554432, //对应flags
sizeof(__Block_byref_person_0), //当前结构体所需内存大小
__Block_byref_id_object_copy_131, //copy函数
__Block_byref_id_object_dispose_131,//dispose函数
objc_msgSend(objc_msgSend(objc_getClass("XLPerson"),
sel_registerName("alloc")),
sel_registerName("init")) //通过objc_msgSend创建XLPerson对象,并且将对象的指针传入结构体中
};
可以明显看出,在结构体__Block_byref_person_0中,存在如下成员变量
- isa指针,此处赋值为0,同时也能说明此结构体也是一个OC对象
- forwarding指针,指向结构体自身的内存地址
- flags,标志位,此处传33554432
- size,结构体大小,通过sizeof(__Block_byref_person_0)获得,此处可以简单计算出结构体所需内存大小为48个字节
- __Block_byref_id_object_copy,copy函数,因为在结构体中引用到了person对象,所以调用此方法来根据person指针的引用类型决定是否对person对象进行retain操作,此处person对象是使用__strong来修饰,所以copy函数的作用就是对person对象进行一次retain操作,引用计数+1。
- __Block_byref_id_object_dispose_131,dispose函数,在结构体从内存中移除的时候,会调用dispose函数,对person对象进行一次release操作,引用计数-1
- person指针,因为我们外部创建的是XLPerson的实例对象,所以结构体内部直接保存了person指针来指向我们创建的XLPerson对象。
因为使用__block修饰的XLPerson对象的指针存放在了结构体内部,所以需要使用copy函数和dispose函数来管理对象的内存。
__block修饰基本数据类型auto变量
如果使用__block来修饰基本数据类型的auto变量,就会将变量封装成__Block_byref_a_1类型的结构体,内部结构如下
#将变量a封装成结构体__Block_byref_a_1
struct __Block_byref_a_1 {
void *__isa; //isa指针
__Block_byref_a_1 *__forwarding;//forwarding指针
int __flags; //标识位
int __size; //结构体大小
int a; //变量a
};
//封装变量a
__Block_byref_a_1 a = {
0, //isa,传0
(__Block_byref_a_1 *)&a, //传入当前结构体a的地址
0, //flags
sizeof(__Block_byref_a_1), //结构体的大小
10 //外部变量a的值
};
相对于__block修饰auto对象,如果修饰基本数据类型,则结构体中少了copy函数和dispose函数,因为基本数据类型不需要进行内存管理,所以不需要调用这两个函数。
- isa指针,此处传0
- forwarding指针,指向结构体自身的内存地址
- flags,此处传0
- size,结构体的大小,使用sizeof(__Block_byref_a_1)来获取,此处结构体占用内存大小为32个字节(结构体本身需要的内存大小为28个字节)
- a,保存了外部变量a的值,此处为10。
__block的使用总结
-
__block可以用来解决在block内部无法修改auto变量值的问题。
-
__block只能用来修饰auto类型变量,无法用来修饰全局变量、静态变量等等 使用__block修饰的auto变量,编译器会将此变量封装成一个结构体(其实也是一个对象),结构体内部有以下几个成员变量
- isa指针
- forwarding指针,指向自身内存地址
- *flags
- *size,结构体的大小
- *val(使用的外部变量,如果是基本数据类型,就是变量的值,如果是对象类型,就是指向对象的指针)
-
__block修饰基本数据类型的auto变量,例如__block int a,那么封装的结构体内部成员变量如上,如果是修饰对象类型的auto变量,如__block XLPerson *person,那么生成的结构体中会多出copy和dispose两个函数,用来管理person对象的内存。
block循环引用的问题
在使用block时,如果block作为一个对象的属性,并且在block中也使用到了这个对象,则会产生循环引用,导致block和对象相互引用,无法释放。
typedef void(^TestBlock)(void);
@interface XLPerson : NSObject
@property(nonatomic, copy)NSString *name;
@property(nonatomic, copy)TestBlock block;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
XLPerson *person = [[XLPerson alloc] init];
person.name = @"张三";
person.block = ^{
NSLog(@"%@",person.name);
};
person.block();
}
return 0;
}
解决方式有两种(此处主要讲解ARC的情况下):
- 使用__weak来修饰对象
__weak typeof(person) weakPerson = person;
person.block = ^{
NSLog(@"%@",weakPerson.name);
};
- 使用__unsafe_unretained来修饰对象
__unsafe_unretained XLPerson *weakPerson = person;
person.block = ^{
NSLog(@"%@",weakPerson.name);
};
__weak和__unsafe_unretained的区别
__weak和__unsafe_unretained最终的效果都是能使block不对外部访问的对象形成强引用,而是形成弱引用。也就是说外部对象的引用计数不会增加。但是__weak和__unsafe_unretained也有区别,__weak在对象被销毁后会自动将weak指针置为nil,而__weak和__unsafe_unretained修饰的对象在被销毁后,指针是不会被清空的,如果后续访问到了这个指针,会报野指针的错误,因此在遇到循环引用的时候,优先使用__weak来解决
小结
block 的本质
带有自动变量(局部变量)的匿名函数。 会捕获访问的变量
blok的变量捕获机制(auto 是默认的关键字和static对应)
由此我们就可以得出结论,block中有变量捕获的机制
- 当访问局部变量的时候,会将局部变量的值捕获到block中,存放在一个同名的成员变量中。
- 当访问静态变量时,会将静态变量的地址捕获到block中,存放在一个同名的成员变量中
- 当访问全局变量时,因为全局变量是一直存在,不会销毁,所以在block中直接访问全局变量,不需要进行捕获
-
block作为返回值时,会自动进行copy
-
使用__strong类型的指针指向block时,会执行copy操作
-
block作为Cocoa API中含有usingBlock的方法的参数时,会执行copy操作
-
block作为GCD方法的参数时会执行copy操作
- __weak和__unsafe_unretained也有区别,__weak在对象被销毁后会自动将weak指针置为nil,而__weak和__unsafe_unretained修饰的对象在被销毁后,指针是不会被清空的,如果后续访问到了这个指针,会报野指针的错误
block的类型
block的类型 | block执行的操作 |
---|---|
NSGlobalBlock | 没有访问auto类型的变量 |
NSStackBlock | 访问了auto类型的变量 |
NSMallocBlock | __NSStackBlock__类型的block执行了copy操作 |
block的copy 效果
什么情况下会对block进行copy
block 访问对象类型
block 访问了对象,就会自动捕获。需要对其进行内存管理。 block内部会根据对象的修饰词,进行内部修饰。
__main_block_desc_0结构体 内新增了2个函数copy和dispose两个函数指针,控制引用计数
函数 | 调用时机 |
---|---|
copy | 栈上的block被复制到堆上 |
dispose | 堆上的block被释放时 |
__block的用法
__block的目的
在block内部来修改外部变量的值,当然,block只能用来修饰auto变量,不能用来修饰全局变量和静态变量。
__block 原理
使用__block修饰的auto变量,编译器会将此变量封装成一个结构体(其实也是一个对象),结构体内部有以下几个成员变量 isa,val(使用的外部变量,如果是基本数据类型,就是变量的值,如果是对象类型,就是指向对象的指针)
果是修饰对象类型的auto变量,那么生成的结构体中会多出copy和dispose两个函数,用来管理person对象的内存。
block的循环引用
为什么blcoK外面用__Weak 修饰过对象后(比如self),在block 内部还要用 __strong 修饰
weakSelf是为了block不持有self,避免循环引用,而再声明一个strongSelf是因为一旦进入block执行,就不允许self在这个执行过程中释放。block执行完后这个strongSelf会自动释放,没有循环引用问题。