【面试题】block 相关
block 的本质
block 的本质是封装了函数调用及函数调用环境的 OC 对象。
可以执行如下代码验证:
#import <Foundation/Foundation.h>
/*
终端执行:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
获取的 block 相关结构体
*/
// impl 结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// block 描述信息
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
// block 结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
};
//__main_block_impl_0 结构体初始化
//((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a))();
// 函数定义(需传入 __main_block_impl_0 结构体指针)
//static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// int a = __cself->a; // bound by copy
// NSLog((NSString *)&__NSConstantStringImpl__var_folders_vz_ktjs0_fn5dg59rp_kggzsch80000gn_T_main_995593_mi_0, a);
//}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 声明变量供 block 捕获
int a = 10;
// 声明 block
void (^block)(void) = ^{
NSLog(@"%s:a: %d", __func__, a);
};
// 强转为 __main_block_impl_0 结构体指针
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
// 打印变量,证明变量是否捕获成功
NSLog(@"__main_block_impl_0->a: %d", blockImpl->a);
// 获取函数指针
void (*func)(struct __main_block_impl_0 *) = blockImpl->impl.FuncPtr;
// 直接通过函数指针调用,证明是否执行 block 内部的代码
func(blockImpl);
}
return 0;
}
打印输出:
外部变量的访问
变量类型 | 是否捕获到 block 内部 | 访问方式 |
---|---|---|
局部 auto 变量 | 捕获 | 值传递 |
局部 static 变量 | 捕获 | 指针传递 |
全局变量 | 不捕获 | 直接访问 |
#import <Foundation/Foundation.h>
int c = 30;
static int d = 40;
void blockCapture(void) {
int a = 10;
static int b = 20;
void (^block)(void) = ^{
NSLog(@"%d---%d---%d---%d", a, b, c, d);
};
a = 100;
b = 200;
c = 300;
d = 400;
block();
}
int c = 30;
static int d = 40;
struct __blockCapture_block_impl_0 {
struct __block_impl impl;
struct __blockCapture_block_desc_0* Desc;
int a;
int *b;
};
static void __blockCapture_block_func_0(struct __blockCapture_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_vz_ktjs0_fn5dg59rp_kggzsch80000gn_T_main_426782_mi_2, a, (*b), c, d);
}
block 的类型
block 的类型 | 环境 | 副本源的配置存储域 | 复制效果 |
---|---|---|---|
__NSGlobalBlock__(_NSConcreteGlobalBlock) | 没有访问 auto 变量 | 程序的数据区域(.data 区) | 什么也不做 |
__NSStackBlock__(_NSConcreteStackBlock) | 访问了 auto 变量 | 栈 | 从栈复制到堆 |
__NSMallocBlock__(_NSConcreteMallocBlock) | __NSStackBlock__ 调用了 copy | 堆 | 引用计数增加 |
block 的 copy
在 ARC 环境下,编译器会根据情况自动将栈上的 block 复制到堆上,比如以下情况
- block 作为函数返回值时
- 将 block 赋值给 __strong 指针时
- block 作为 Cocoa API 中方法名含有 usingBlock 的方法参数时
- block 作为 GCD API 的方法参数时
MRC 下 block 属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC 下 block 属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
block 访问对象类型的 auto 变量
当 block 内部访问了对象类型的 auto 变量时
- 如果 block 是在栈上,将不会对 auto 变量产生强引用
- 如果 block 被拷贝到堆上
- 会调用 block 内部的 copy 函数
- copy 函数内部会调用 _Block_object_assign 函数
- _Block_object_assign 函数会根据 auto 变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
- 如果 block 从堆上移除
- 会调用 block 内部的 dispose 函数
- dispose 函数内部会调用 _Block_object_dispose 函数
- _Block_object_dispose 函数会自动释放引用的 auto 变量 (release)
#import <Foundation/Foundation.h>
@interface JQPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation JQPerson
- (void)dealloc {
NSLog(@"%@: dealloc", self.name);
}
@end
typedef void(^JQBlock)(void);
void blockCopy(void) {
JQBlock block;
{
JQPerson *strongPerson = [[JQPerson alloc] init];
strongPerson.name = @"strong person";
JQPerson *person = [[JQPerson alloc] init];
person.name = @"weak person";
__weak JQPerson *weakPerson = person;
block = ^{
NSLog(@"-->%@", strongPerson.name);
NSLog(@"-->%@", weakPerson.name);
};
}
block();
NSLog(@"-------");
}
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
struct __blockCopy_block_impl_0 {
struct __block_impl impl;
struct __blockCopy_block_desc_0* Desc;
JQPerson *__strong strongPerson;
JQPerson *__weak weakPerson;
__blockCopy_block_impl_0(void *fp, struct __blockCopy_block_desc_0 *desc, JQPerson *__strong _strongPerson, JQPerson *__weak _weakPerson, int flags=0) : strongPerson(_strongPerson), weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockCopy_block_copy_0(struct __blockCopy_block_impl_0*dst, struct __blockCopy_block_impl_0*src) {
_Block_object_assign((void*)&dst->strongPerson, (void*)src->strongPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __blockCopy_block_dispose_0(struct __blockCopy_block_impl_0*src) {
_Block_object_dispose((void*)src->strongPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __blockCopy_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __blockCopy_block_impl_0*, struct __blockCopy_block_impl_0*);
void (*dispose)(struct __blockCopy_block_impl_0*);
} __blockCopy_block_desc_0_DATA = { 0, sizeof(struct __blockCopy_block_impl_0), __blockCopy_block_copy_0, __blockCopy_block_dispose_0};
__block 修饰符
- __block 可以用于解决 block 内部无法修改 auto 变量值的问题
- __block 不能修饰全局变量、静态变量 (static)
- 编译器会将 __block 修饰的变量包装成一个对象
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;
void (^block)(void) = ^{
a = 20;
};
block();
}
return 0;
}
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 20;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
__block 的内存管理
- 当 block 在栈上时,并不会对 __block 变量产生强引用
- 当 block 被 copy 到堆时
- 会调用 block 内部的 copy 函数
- copy 函数内部会调用 _Block_object_assign 函数
- _Block_object_assign 函数会对 __block 变量形成强引用(retain)
- 当block从堆中移除时
- 会调用 block 内部的 dispose 函数
- dispose 函数内部会调用 _Block_object_dispose 函数
- _Block_object_dispose 函数会自动释放引用的 __block 变量(release)
__block 的 __forwarding 指针
block 的循环引用
- __weak
__weak typeof(self) weakSelf = self; self.block = ^{ NSLog(@"%@", weakSelf); };
- __block 修饰,并在 block 内手动去除引用(必须要调用 block)
__block id blockSelf = self; self.block = ^{ NSLog(@"%@", blockSelf); blockSelf = nil; }; self.block();