一、block的本质
block本质上也是一个OC对象,它内部也有一个isa指针
block是封装了函数调用以及函数调用环境的OC对象
block内部代码会封装到_block_func_0函数中,函数地址保存在FuncPtr中
执行block内部代码时是通过FuncPtr找到函数地址进行调用
block的底层结构如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 指向要执行的函数地址
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//构造函数(类似于OC的init方法),返回结构体对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
二、block的变量捕获机制
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
1、block内部访问auto变量时会将auto变量捕获到block内部,block外部修改auto变量的值并不会影响block内部,所以是值捕获
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^block)(void) = ^ {
NSLog(@"age = %d", age); // age = 10
};
age = 20;
block();
}
return 0;
}
// 底层C++代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
/*
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
age);
*/
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 20;
// block->FuncPtr(block);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; //变量捕获
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_v9_dtds2kd515b0fcvr9_xqn_mm0000gn_T_main_85e3bc_mi_0, age);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
2、block访问static变量时会将auto变量捕获到block内部,block外部修改static变量的值会影响block内部,所以是指针捕获
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int age = 10;
void (^block)(void) = ^ {
NSLog(@"age = %d", age); // age = 20
};
age = 20;
block();
}
return 0;
}
// 底层C++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3、block访问全局变量时不会将全局变量捕获到block内部,而是直接访问
int age = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^ {
NSLog(@"age = %d", age); // age = 20
};
age = 20;
block();
}
return 0;
}
//底层C++代码
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;
}
};
4、无参方法内部block访问self会将self捕获到block内部,从底层C++代码可以看出方法test有两个默认参数self、_cmd,它们也是auto类型的局部变量
#import "QLStudent.h"
@implementation QLStudent
- (void)test {
void(^block)(void) = ^ {
NSLog(@"===========%p", self);
};
block();
}
@end
//底层C++代码
static void _I_QLStudent_test(QLStudent * self, SEL _cmd) {
void(*block)(void) = ((void (*)())&__QLStudent__test_block_impl_0((void *)__QLStudent__test_block_func_0, &__QLStudent__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
三、block的类型
block类型 | 环境 |
---|---|
NSGlobalBlock | 没有访问auto变量 |
NSStackBlock | 访问了auto变量 |
NSMallocBlock | NSStackBlock调用了copy |
1、NSGlobalBlock:存放在数据段
NSGlobalBlock调用copy还是NSGlobalBlock
// 没有访问auto变量
void (^block)(void) = ^ {
NSLog(@"Hello world!");
};
NSLog(@"%@", [block class]); // __NSGlobalBlock__
NSLog(@"%@", [[block class] superclass]); // NSBlock
NSLog(@"%@", [[[block class] superclass] superclass]); // NSObject
2、NSStackBlock:存放在栈区
NSStackBlock调用copy,从栈复制到堆
// 访问了auto变量
int num = 10;
void (^block)(void) = ^ {
NSLog(@"num = %d", num);
};
NSLog(@"%@", [block class]); // __NSStackBlock__
3、NSMallocBlock:存放在堆区
NSMallocBlock调用copy,引用计数增加
// NSStackBlock调用了copy
int num = 10;
void (^block)(void) = [^ {
NSLog(@"num = %d", num);
} copy];
NSLog(@"%@", [block class]); // __NSMallocBlock__
四、block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
1、block作为函数的返回值时
2、将block赋值给__strong指针时
3、block作为Cocoa API中方法名含有usingBlock的方法参数时,例如
NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj,
NSUInteger idx, BOOL * _Nonnull stop) {
}];
4、block作为GCD API的方法参数时,例如:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
五、解决block的循环引用
1、用__weak、__unsafe_unretained
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__weak typeof(self)weakSelf = self;
self.block = ^ {
NSLog(@"%p", weakSelf);
};
__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
__unsafe_unretained id weakSelf = self;
self.block = ^ {
NSLog(@"%p", weakSelf);
};
2、用__block解决(必须要调用block)
__block QLPerson *person = [[QLPerson alloc] init];
person.age = 10;
person.block = ^ {
NSLog(@"age is %d", person.age);
person = nil;
};
person.block();