一、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有个变量捕获机制
![](https://img.haomeiwen.com/i3250482/5622d04a1a040271.jpg)
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++代码
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;
}
};
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;
}
};
三、block的类型
block类型 | 环境 |
---|---|
NSGlobalBlock | 没有访问auto变量 |
NSStackBlock | 访问了auto变量 |
NSMallocBlock | NSStackBlock调用了copy |
四、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();
网友评论