block的本质
1.block的基本用法
// 不带参数无返回值的blockvoid (^block)(void) = ^{NSLog(@"Hello, World!");};block();// 带参数无返回值的blockvoid (^block)(int, int) = ^(int a , int b) {NSLog(@"this is a block!");}; block(10, 20);2.将block代码转换成C++文件后发现,生成了一个__main_block_impl_0类型的结构体,block是指向这个结构体的指针
int age = 20; void (^block)(int, int) = ^(int a , int b){ NSLog(@"this is a block! -- %d", age); NSLog(@"this is a block!"); NSLog(@"this is a block!"); NSLog(@"this is a block!");};block(10, 10);// 转换后的C++代码int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int age = 10; // 定义block变量 // block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age) void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); // 执行block内部代码 // __main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里 // (block->FuncPtr)(block, 10, 10) ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10); } return 0;}3.__main_block_impl_0类型的结构体,里面包含了__block_impl类型的结构体变量impl和__main_block_desc_0类型的结构体变量Desc,一个返回值为__main_block_impl_0类型的构造函数,还会生成一个age来存储外面引用的值
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int age; // 构造函数(类似oc的init) // __main_block_func_0的地址传给fp // : age(_age)语法会自动将_ag赋值给age __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) { impl.isa = &_NSConcreteStackBlock; // isa指向的_NSConcreteStackBlock就是当前block类型 impl.Flags = flags; impl.FuncPtr = fp; // 保存的就是__main_block_func_0的地址,也就是block执行逻辑的函数 Desc = desc; // 保存的是&__main_block_desc_0_DATA的地址(主要存储的就是__main_block_impl_0的大小) // 默认返回的就是__main_block_impl_0结构体 }};4.__block_impl类型的结构体里包含isa指针,说明block也是一个OC对象。在__main_block_impl_0的构造函数中isa指向的是_NSConcreteStackBlock类型的地址,侧面说明这个类型也是当前编译时的block的真实类型
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;};5.__main_block_func_0是一个封装了block执行逻辑代码的函数,在__main_block_impl_0的构造函数中通过参数void *fp赋值给FuncPtr指针变量,来保存执行代码的地址
// 封装了block执行逻辑的函数static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) { int age = __cself->age; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age); NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_1); NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_2); NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_3);}6.__main_block_desc_0这个类型的结构体变量__main_block_desc_0_DATA里面的reserved赋值为0,Block_size赋值为sizeof(struct __main_block_impl_0),也就是当前__main_block_impl_0这个结构体的大小。在__main_block_impl_0的构造函数中通过参数desc赋值给Desc
static struct __main_block_desc_0 { size_t reserved; // 0 size_t Block_size; // 计算的就是__main_block_impl_0这个结构体的大小} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};7.__main_block_impl_0的构造函数中 : age(_age)语法会自动将_age赋值给变量int age来保存。而且转换后的调用__main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里
总结
block本质上也是一个OC对象,它内部也有个isa指针block是封装了函数调用以及函数调用环境的OC对象
block的本质结构可以概括为下面这张图
block的变量捕获
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
局部变量默认是被auto修饰的,表示自动变量,离开作用域就销毁。block的捕获该变量是值传递局部变量被static修饰,会一直在内存中不被释放,block的捕获该变量是指针传递全局变量因为是一直都在内存中存在的,所以不用捕获
block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型__NSGlobalBlock__ ( _NSConcreteGlobalBlock ) - __NSStackBlock__ ( _NSConcreteStackBlock ) - __NSMallocBlock__ ( _NSConcreteMallocBlock)block的真实类型都是以运行时为准的,通过Clang编译出的C++类型不是最准确的,因为在运行时又会做了一些变动和处理。而且现在LLVM只会生成一种中间文件,和Clang生成的文件有差异通过下面代码观察block的对应输出
int main(int argc, const char * argv[]) { @autoreleasepool { void (^block)(void) = ^{ NSLog(@"Hello"); }; NSLog(@"%@", [block class]); NSLog(@"%@", [[block class] superclass]); NSLog(@"%@", [[[block class] superclass] superclass]); } return 0;}// 对应的输出:__NSGlobalBlock__ NSBlock NSObject不同内存区域对应的block类型不同
数据段对应的是__NSGlobalBlock__类型的block堆段对应的是__NSMallocBlock__类型的block栈段对应的是__NSStackBlock__类型的block
不同操作对应的block类型不同
没有访问自动变量的block的类型是__NSGlobalBlock__访问了自动变量的block的类型是__NSStackBlock____NSStackBlock__的block调用了copy后类型会变为__NSMallocBlock__
每一种类型的block调用copy后的结果如下所示
修改Xcode的Build Setting->Objective-C Automatic Reference Counting为No,使编译环境为MRC,然后输出下面代码可以查看block对应的类型
int main(int argc, const char * argv[]) { @autoreleasepool { int a = 10; // 堆:动态分配内存,需要程序员申请申请,也需要程序员自己管理内存 void (^block1)(void) = ^{ NSLog(@"Hello"); }; int age = 10; void (^block2)(void) = ^{ NSLog(@"Hello - %d", age); }; NSLog(@"%@ %@ %@", [block1 class], [[block2 copy] class], [^{ NSLog(@"%d", age); } class]); } return 0;}// 对应的输出:__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__注意:block2在MRC环境下的类型为__NSStackBlock__,是存储在栈段的。只有通过copy修饰才会变成__NSMallocBlock__,存储在堆中。在ARC环境下即使不用copy修饰类型也是__NSMallocBlock__,因为编译器会视情况自动进行copy操作
block的copy操作
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上
1.block作为函数返回值时
如果不进行copy操作,myblock内部的block返回值作用域一结束就会被释放
typedef void (^Block)(void);Block myblock(){ int age = 10; return ^{ NSLog(@"---------%d", age); };}int main(int argc, const char * argv[]) { @autoreleasepool { Block block = myblock(); block(); NSLog(@"%@", [block class]); // __NSMallocBlock__ } return 0;}2.将block赋值给__strong指针时
typedef void (^Block)(void);int main(int argc, const char * argv[]) { @autoreleasepool { int age = 10; // 强指针Block block Block block = ^{ NSLog(@"---------%d", age); }; NSLog(@"%@", [block class]); // __NSMallocBlock__ } return 0;}3.block作为Cocoa API中方法名含有usingBlock的方法参数时
NSArray *array = @[];[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { }];4.block作为GCD API的方法参数时
static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{ }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });不同环境下block属性的写法
1.MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);2.ARC下block属性的建议写法
// 因为编译器会自动视情况进行copy操作,所以两种写法都没问题,只是为了统一规范建议使用copy来修饰属性@property (strong, nonatomic) void (^block)(void);@property (copy, nonatomic) void (^block)(void);对象类型的auto变量
当block内部访问了对象类型的auto变量时1.如果block是在栈上,将不会对auto变量产生强引用
@interface Person : NSObject@property (assign, nonatomic) int age;@@implementation Person- (void)dealloc{ [super dealloc]; NSLog(@"Person - dealloc");}@typedef void (^Block)(void);int main(int argc, const char * argv[]) { @autoreleasepool { Block block; { Person *person = [[Person alloc] init]; person.age = 10; block = ^{ NSLog(@"---------%d", person.age); }; // MRC环境下对应的内存管理 [person release]; NSLog(@"------%@", [block class]); } // 在这里打断点,由于MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放 NSLog(@"------"); } return 0;}2.如果block被拷贝到堆上,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作
@interface Person : NSObject@property (assign, nonatomic) int age;@@implementation Person- (void)dealloc{ NSLog(@"Person - dealloc");}@typedef void (^Block)(void);int main(int argc, const char * argv[]) { @autoreleasepool { Block block; { Person *person = [[Person alloc] init]; person.age = 10; // __strong Person *weakPerson = person; __weak Person *weakPerson = person; block = ^{ NSLog(@"---------%d", weakPerson); }; NSLog(@"------%@", [block class]); } // 在这里打断点,在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放 NSLog(@"------"); } return 0;}将上面代码文件转换成C++文件可以看出,block内部的__main_block_desc_0结构体会调用copy函数,copy函数内部会调用_Block_object_assign函数,而_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person *__strong person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};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*);} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}3.如果block从堆上移除,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}4.block只有引用的是基本数据类型才不会生成copy和dispose函数
5.如果用static修饰对象类型,那么生成的C++代码如下
Block block; { static NSString *string = @"haha"; block = ^{ NSLog(@"---------%@", string); };}// 生成的C++代码 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; // string变量的类型是NSString ** NSString *__strong *string; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *__strong *_string, int flags=0) : string(_string) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};注意:代码里有__weak,转换C++文件可能会报错cannot create __weak reference in file using manual reference,可以指定ARC、指定运行时系统版本
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 源文件```### __block修饰符#### __block修饰基本数据类型看下面代码,怎样可以在`block`内部修改`age`的值typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10; Block block1 = ^{ // age = 20; NSLog(@"age is %d", age); }; block1(); NSLog(@"age的内存地址 - %p", &age);}return 0;}
1.用`static`来修饰`age属性`,`block`内部引用的是`age`的地址值,可以根据地址去修改`age`的值。但不好的是`age属性`会一直存放在内存中不销毁,造成多余的内存占用,而且会改变`age属性`的性质,不再是一个`auto变量`了static int age = 10;
2.用`__block`来修饰属性,底层会生成`__Block_byref_age_0`类型的结构体对象,里面存储着`age`的真实值__block int age = 10;
3.转换成`C++文件`来查看内部结构,会根据`__main_block_impl_0`里生成的`age`对象来修改内部的成员变量`age`而且在外面打印的`age`属性的地址值也是`__Block_byref_age_0`结构体里的成员变量`age`的地址,目的就是不需要知道内部的真实实现,所看到的就是打印出来的值struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; // 保存的自己的地址
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// __Block_byref_age_0 age = {0, &age, 0, sizeof(__Block_byref_age_0), 10}; __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; Block block1 = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);}return 0;}
##### 总结:- `__block`可以用于解决`block`内部无法修改`auto`变量值的问题- 编译器会将`__block`变量包装成一个对象- 其实修改的变量是`__block`生成的对象里面存储的变量的值,而不是外面的`auto变量`,但是内部生成的相同的变量的地址和外面的`auto变量`地址值是一样的,所以修改了内部的变量也会修改了外面的`auto变量`- `__block`不能修饰全局变量、静态变量(static)##### __block的内存管理1.程序编译时,`block`和`__block`都是在栈中的,这时并不会对`__block`变量产生强引用2.因为`__block`也会包装成`OC对象`,所以`block`底层也会生成`copy函数`和`dispose函数` 3.当`block`被`copy`到堆时,会调用`block`内部的`copy函数`,`copy函数`内部会调用`_Block_object_assign`函数,`_Block_object_assign`函数会对`__block`变量形成强引用(retain)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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->age, (void)src->age, 8/BLOCK_FIELD_IS_BYREF/);}
实际上这时`__block`修饰的变量因为被包装成了`OC对象`,所以也会被拷贝到堆上,如果再有`block`强引用`__block`,由于`__block`变量已经拷贝到堆上了,就不会再拷贝了,下图可以很好的表达出关系![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024131050-2137722604.jpg)3.当`block`从堆中移除时,会调用`block`内部的`dispose函数`,`dispose函数`内部会调用`_Block_object_dispose`函数,`_Block_object_dispose`函数会自动释放引用的`__block`变量(release)static void __main_block_dispose_0(struct __main_block_impl_0src) {_Block_object_dispose((void)src->age, 8/BLOCK_FIELD_IS_BYREF/);}
如果有多个`block`同时持有着`__block`变量,那么只有所有的`block`都从堆中移除了,`__block`变量才会被释放![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024131097-86614401.jpg)##### __block和OC对象在block中的区别看下面的代码,在`block`中的本质区别是什么__block int age = 10;
NSObject *obj = [[NSObject alloc] init];
Block block1 = ^{
age = 20;
NSLog(@"age is %d", age);
NSLog(@"obj is %p", obj);
};
转成`C++文件`发现,`__block`生成的对象就是强引用,而`NSObject`对象会根据修饰符`__strong`或者`__weak`来区分是否要进行`retain操作`struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *__strong obj;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, __Block_byref_age_0 *_age, int flags=0) : obj(_obj), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->age, (void)src->age, 8/BLOCK_FIELD_IS_BYREF/);_Block_object_assign((void)&dst->obj, (void)src->obj, 3/BLOCK_FIELD_IS_OBJECT/);}
**注意:`__weak`不能修饰基本数据类型,编译器会报`__weak' only applies to Objective-C object or block pointer types; type here is 'int'`警告**##### __forwarding指针- 在栈中,`__block`中的`__forwarding指针`指向自己的内存地址- 复制到堆中之后,`__forwarding指针`指向堆中的`__block`- 堆中的`__forwarding`指向堆中的`__block`- 这样的目的都是为了不论访问的`__block`是在栈上还是在堆上,都可以通过`__forwarding指针`找到存储在堆中的`auto变量`![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130733-2067416101.jpg)#### __block修饰对象类型1.看下面代码,用`__block`修饰的对象类型什么时候被释放typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{ Person *person = [[Person alloc] init]; person.age = 10; __block Person *weakPerson = person; block = ^{ NSLog(@"---------%d", weakPerson.age); }; NSLog(@"------%@", [block class]); } // 在这里打断点观察person是否会被释放 NSLog(@"------");}return 0;}
2.转换成`C++文件`可以发现,`__block`底层生成的结构体里面会引用着该对象类型,并且默认是用`__strong`来修饰,而且内部也会对应的生成`copy`和`dispose`函数struct __Block_byref_weakPerson_0 {
void __isa;
__Block_byref_weakPerson_0 __forwarding;
int __flags;
int __size;
void (__Block_byref_id_object_copy)(void, void);
void (__Block_byref_id_object_dispose)(void*);
Person *__strong weakPerson;
};
3.我们看`main函数`里会将`__Block_byref_id_object_copy_131`和`__Block_byref_id_object_dispose_131`赋值给`__Block_byref_weakPerson_0`这个结构体对象int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Block block;
{ Person *person = ((Person *(*)(id, SEL))(void *)objc_msgS)((id)((Person *(*)(id, SEL))(void *)objc_msgS)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")); ((void (*)(id, SEL, int))(void *)objc_msgS)((id)person, sel_registerName("setAge:"), 10); // __Block_byref_weakPerson_0 weakPerson = {0, &weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person}; __attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person}; block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakPerson_0 *)&weakPerson, 570425344)); NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_kcs2c07n3mqd02d77tvjgtjr0000gn_T_main_8be7f8_mi_1, ((Class (*)(id, SEL))(void *)objc_msgS)((id)block, sel_registerName("class"))); } NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_kcs2c07n3mqd02d77tvjgtjr0000gn_T_main_8be7f8_mi_2);}return 0;}
4.找到这两个值能发现也是分别会调用`_Block_object_assign`和`_Block_object_dispose`这两个函数,而且传的对象就是`__Block_byref_weakPerson_0`内部的`weakPerson`这个对象,也就是说这个结构体内部也会对`weakPerson`这个对象进行着`retain`和`release`的操作static void __Block_byref_id_object_copy_131(void *dst, void src) {
_Block_object_assign((char)dst + 40, *(void * ) ((char)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void src) {
_Block_object_dispose((void * ) ((char)src + 40), 131);
5.我们把第一段代码中的`weakPerson`加上`__weak`修饰符,再运行程序会发现,当作用域结束后,`person`对象也会被释放了typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{ Person *person = [[Person alloc] init]; person.age = 10; __block __weak Person *weakPerson = person; block = ^{ NSLog(@"---------%d", weakPerson.age); }; NSLog(@"------%@", [block class]); } // 在这里打断点观察person是否会被释放 NSLog(@"------");}return 0;}
6.我们转换成`C++文件`能发现,`__Block_byref_weakPerson_0`里面的`person`对象修饰符变成了`__weak`struct __Block_byref_weakPerson_0 {
void __isa;
__Block_byref_weakPerson_0 __forwarding;
int __flags;
int __size;
void (__Block_byref_id_object_copy)(void, void);
void (__Block_byref_id_object_dispose)(void*);
Person *__weak weakPerson;
};
##### 总结:- `__block`修饰的对象类型也会生成一个新的结构体对象,并且只会被`block`进行强引用,同`__block`修饰基本数据类型是一样的- `__block`内部也会生成该对象类型的成员变量,而且会根据不同的修饰符`__strong`和`__weak`来对应着该对象类型是否被强引用- `__block`内部也会生成`copy`和`dispose`函数- 当`__block`变量被`copy`到堆时,会调用`__block`变量内部的`copy函数`,`copy函数`内部会调用`_Block_object_assign`函数,`_Block_object_assign`函数会根据所指向对象的修饰符`(__strong、__weak、__unsafe_unretained)`做出相应的操作,形成强引用(retain)或者弱引用- 如果`__block`变量从堆上移除,会调用`__block`变量内部的`dispose函数`,`dispose函数`内部会调用`_Block_object_dispose`函数,`_Block_object_dispose`函数会自动释放指向的对象(release)**注意:在MRC环境下即使用\_\_block修饰,\_\_block内部只会对auto变量进行弱引用,无论加不加__weak,block还没有释放,\_\_block修饰的变量就已经释放了,这点和在ARC环境下不同**### 循环引用`block`在使用中很容易就会造成循环引用问题,例如下面的代码typedef void (^Block) (void);
@interface Person : NSObject
@property (copy, nonatomic) Block block;
@property (assign, nonatomic) int age;
(void)test;
@@implementation Person
(void)test {
// 内部循环引用
self.block = ^{
NSLog(@"age is %d", self.age);
};
}
@int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
// 循环引用 person.block = ^{ NSLog(@"age is %d", person.age); };}NSLog(@"111111111111");return 0;}
`person`对象里面的`block`属性强引用着`block`对象,而`block`对象内部也会有一个`person`的成员变量指向这个`Person对象`,这样就会造成循环引用,谁也无法释放![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130088-1235768827.jpg)#### 解决方法##### 在ARC环境下让其中一个指针变成弱引用![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024129668-1990190805.jpg)1.用`__weak`解决,不会产生强引用,当指向的对象销毁时,会自动让指针置为`nil`@implementation Person
(void)test {
__weak typeof(self) weakSelf = self;self.block = ^{
NSLog(@"age is %d", weakSelf.age);
};
}
@int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
// __weak Person *weakPerson = person; __weak typeof(person) weakPerson = person; person.block = ^{ NSLog(@"age is %d", weakPerson.age); };}NSLog(@"111111111111");return 0;}
2.用`__unsafe_unretained`解决,不会产生强引用,但是是不安全的,当指向的对象销毁时,指针存储的地址值不变,仍然是指向着那块已经被回收的内存空间,那么再访问这个这个变量就会造成野指针错误@implementation Person
(void)test {
__unsafe_unretained typeof(self) weakSelf = self;self.block = ^{
NSLog(@"age is %d", weakSelf.age);
};
}
@int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
__unsafe_unretained Person *weakPerson = person; person.block = ^{ NSLog(@"age is %d", weakPerson.age); };}NSLog(@"111111111111");return 0;}
3.用``__block``解决,用`__block`修饰对象会造成三者相互引用造成循环引用,需要手动调用blockint main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
person.age = 10;
person.block = ^{ NSLog(@"age is %d", person.age); person = nil; };}person.block();NSLog(@"111111111111");return 0;}
`block`内部也需要手动将`person`置空,这个`person`是`__block`内部生成的指向`Person对象`的变量![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130614-1682005821.jpg)##### 在MRC环境下1.用`__unsafe_unretained`解决,同`ARC环境下`一样,只是`MRC`不`__weak`Person *person = [[Person alloc] init];
__unsafe_unretained typeof(person) weakPerson = person;
person.block = [^{
NSLog(@"age is %d", weakPerson.age);
} copy];
[person release];
2.用`__block`解决,在`MRC`中,`__block`对象里是不会对`person对象`进行强引用的,所以不会造成循环引用__block Person *person = [[Person alloc] init];
person.age = 10;
person.block = [^{
NSLog(@"age is %d", person.age);
} copy];
[person release];
### 面试题#### 1.看下面代码,分别输入的值是什么int a = 10;
static int b = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int age = 10;static int height = 10;void (^block)(void) = ^{NSLog(@"age is %d, height is %d", age, height);NSLog(@"a is %d, b is %d", a, b);};age = 20;height = 20;a = 20;b = 20;block(); // 输出结果为:age=10,height=20,a=20,b=20 }return 0;}
`age`是自动变量,是值传递`height`表示的是指针传递,`block`捕获的是该变量的地址而`a、b`都为全局变量,所以`block`根本不用捕获,需要时直接拿取当前最新的值就可以了int a = 10;
static int b = 10;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
auto int age = 10;
static int height = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));age = 20;height = 20;a = 20;b = 20; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);}return 0;}
#### 2.看下面代码,block内部会不会捕获self@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
(instancetype)initWithName:(NSString *)name;
@@implementation Person
(void)test
{
void (^block)(void) = ^{
NSLog(@"-------%d", [self name]);
};
block();
}(instancetype)initWithName:(NSString *)name
{
if (self = [super init]) {
self.name = name;
}
return self;
}@
会捕获。因为`self`本质也是一个局部变量,`block`内部会生成一个变量来保存`Person对象`的地址struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 函数都会生成隐式参数self和_cmd
static void _I_Person_test(Person * self, SEL _cmd) {
void (block)(void) = ((void ()())&__Person__test_block_impl_0((void )__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
((void ()(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
#### 3.\_\_block的作用是什么?有什么使用注意点可以将修饰的变量包装成一个对象,解决在`block`内部无法修改外部变量的问题。`__block`内部会进行内存管理,还有在`MRC环境下`是不会对对象进行强引用#### 4.block的属性修饰词为什么是copy?使用block有哪些使用注意?`block`一旦没有进行`copy操作`,就不会在堆上。放到堆上的目的是方便我们来控制他的生命周期,可以更有效的进行内存管理。注意循环引用