目标:
- 熟悉Block底层原理
- 熟悉内存拷贝相关的知识
如何深入学习相关概念呢? 将OC代码转化成C++代码,查看其内部实现。 面试过程中,每道题都是有对应分数的,一定要尽可能答的漂亮。
什么是Block
常见面试题:
- 什么是Block?
- 你对Block的调用是怎么理解的?
- 截获变量是Block的一大特性,系统对于Block的截获是怎么实现的呢?
- 我们添加__block修饰符做什么事情呢?
- 什么时候需要对Block 进行一个copy操作,栈Block和堆Block是否了解呢?
- Block的循环引用如何解决
- block使用注意事项有哪些
知识相关性,引用计数相关(修饰符,访问安全,循环引用)__block修饰符的本质
1. 什么是Block?
Block是一种带有自动变量(局部变量)的匿名函数,功能上看类似于C语言上的函数指针,但比函数指针更强大,它可以捕获其定义时所在作用域的自动变量。本质上是一个对象,封装了执行函数和上下文。底层上看Block时一个struct结构体,包含了函数指针、捕获结构体指针以及一些额外信息。 分为三种类型:
内存管理:Block 有三种类型,分别对应不同的内存区域:
- _NSConcreteStackBlock:栈上的 Block,当定义 Block 的函数返回时,该 Block 会被销毁。这种类型的 Block 通常在函数内部定义且未进行 copy 操作时存在。
- _NSConcreteGlobalBlock:全局的 Block,存放在数据段,类似于全局变量。当 Block 没有捕获任何自动变量时,它会被优化为全局 Block。
- _NSConcreteMallocBlock:堆上的 Block,需要程序员手动或者自动调用 copy 操作将栈上的 Block 复制到堆上,以延长其生命周期。ARC 环境下,编译器会自动在合适的地方插入 copy 操作。
捕获方式分为: 无捕获、值捕获、引用捕获。
源码解析
+ (void)testBlock1{
int multiplier = 6;
int (^Block)(int) = ^int(int num) {
return num *multiplier;
};
Block(2);
}
使用:
clang -rewrite-objc file.m
查看编译后的文件内容
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __TestObject__testBlock1_block_impl_0 {
struct __block_impl impl;
struct __TestObject__testBlock1_block_desc_0* Desc;
int multiplier;
__TestObject__testBlock1_block_impl_0(void *fp, struct __TestObject__testBlock1_block_desc_0 *desc, int _multiplier, int flags=0) : multiplier(_multiplier) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static int __TestObject__testBlock1_block_func_0(struct __TestObject__testBlock1_block_impl_0 *__cself, int num) {
int multiplier = __cself->multiplier; // bound by copy
return num *multiplier;
}
static struct __TestObject__testBlock1_block_desc_0 {
size_t reserved;
size_t Block_size;
} __TestObject__testBlock1_block_desc_0_DATA = { 0, sizeof(struct __TestObject__testBlock1_block_impl_0)};
// _C 表示类方法,_I 实例方法 ,TstObject 表示类名,testBlock1表示函数名
static void _C_TestObject_testBlock1(Class self, SEL _cmd) {
int multiplier = 6;
int (*Block)(int) = ((int (*)(int))&__TestObject__testBlock1_block_impl_0((void *)__TestObject__testBlock1_block_func_0, &__TestObject__testBlock1_block_desc_0_DATA, multiplier));
((int (*)(__block_impl *, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, 2);
}
Block调用即是函数的调用。 从Block中取出函数指针传递两个参数Block和需要的参数。
截获变量

- 对于基本数据类型的局部变量截获其值。
- 对于对象类型的局部变量连同所有权修饰符一起截获(强引用)。
- 以指针形式截获局部静态变量。
- 不截获全局变量、静态全局变量。
使用
- (void)method {
// 基本类型的局部变量
int var =1;
// 对象类型的局部变量
__unsafe_unretained id unsafe_obj = nil;
__strong id strong_obj = nil;
//静态局部变量
static int static_var = 3;
void(^Block)(void) = ^{
NSLog(@"局部变量<基本数据类型> var %d",var);
NSLog(@"局部变量<__unsafe_unretained 对象类型> var %@",unsafe_obj);
NSLog(@"局部变量<__strong 对象类型> var %@",strong_obj);
NSLog(@"静态变量 %d",static_var);
NSLog(@"全局变量<基本数据类型> var %d",global_var);
NSLog(@"静态全局变量<基本数据类型> var %d",static_global_var);
// 使用对象类型的 会防止循环引用 从而进行 提示
// NSLog(@"TestObject 对象类型成员 num %d",self.num);
};
Block();
}
clang -rewrite-objc -fobjc-arc file.m
struct __TestObject__method_block_impl_0 {
struct __block_impl impl;
struct __TestObject__method_block_desc_0* Desc;
int var;
__unsafe_unretained id unsafe_obj;
__strong id strong_obj;
int *static_var;
__TestObject__method_block_impl_0(void *fp, struct __TestObject__method_block_desc_0 *desc, int _var, __unsafe_unretained id _unsafe_obj, __strong id _strong_obj, int *_static_var, int flags=0) : var(_var), unsafe_obj(_unsafe_obj), strong_obj(_strong_obj), static_var(_static_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestObject__method_block_func_0(struct __TestObject__method_block_impl_0 *__cself) {
int var = __cself->var; // bound by copy
__unsafe_unretained id unsafe_obj = __cself->unsafe_obj; // bound by copy
__strong id strong_obj = __cself->strong_obj; // bound by copy
int *static_var = __cself->static_var; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_0,var);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_1,unsafe_obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_2,strong_obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_3,(*static_var));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_4,global_var);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_89_hqtxy5r12y19qtg10mhvjrgr0000gn_T_TestObject_86265e_mi_5,static_global_var);
}
__block修饰符
使用场景: 一般情况下,对被截获变量进行赋值操作需要添加__block修饰符。 __block修饰符用于实现对变量的引用捕获,使得block内部可以修改外部变量的值。 在底层__block修饰的变量会被封装成一个结构体,结构体内部包含一个指向变量值的指针。block通过这个指针来访问和修改变量的值,从而实现引用捕获。
__block修饰的变量变成了对象。
+ (void)testBlock1{
__block int multiplier = 6;
int (^Block)(int) = ^int(int num) {
return num *multiplier;
};
Block(2);
}
// 编译成C++文件后
struct __Block_byref_multiplier_0 {
void *__isa;
__Block_byref_multiplier_0 *__forwarding;
int __flags;
int __size;
int multiplier;
};
struct __TestObject__testBlock1_block_impl_0 {
struct __block_impl impl;
struct __TestObject__testBlock1_block_desc_0* Desc;
__Block_byref_multiplier_0 *multiplier; // by ref
__TestObject__testBlock1_block_impl_0(void *fp, struct __TestObject__testBlock1_block_desc_0 *desc, __Block_byref_multiplier_0 *_multiplier, int flags=0) : multiplier(_multiplier->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void _C_TestObject_testBlock1(Class self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_multiplier_0 multiplier = {(void*)0,(__Block_byref_multiplier_0 *)&multiplier, 0, sizeof(__Block_byref_multiplier_0), 6};
int (*Block)(int) = ((int (*)(int))&__TestObject__testBlock1_block_impl_0((void *)__TestObject__testBlock1_block_func_0, &__TestObject__testBlock1_block_desc_0_DATA, (__Block_byref_multiplier_0 *)&multiplier, 570425344));
((int (*)(__block_impl *, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, 2);
}
使用 不等于 赋值
笔试题
// 问题:此处是否需要对array 使用__block进行修饰呢
NSMutableArray * array = [[NSMutableArray alloc] init];
void (^Block)(void) = ^{
[array addObject:@123];
};
Block();
NSLog(@"array = %@",array);
此处不需要,仅仅为使用。 下面这个呢: 局部变量不管对象类型是基本数据类型还是对象类型,进行赋值操作都需要添加__block修饰符。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LyQwDFqe-1598110556261)(evernotecid://E87D830C-ABD9-4F44-8969-DDFD5EDD63B4/appyinxiangcom/16371682/ENResource/p295)]](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9a098226c69047b8a03764e5fd8b29e0~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.jpg#?w=2180&h=414&s=101409&e=png&b=2a2b30)
对变量进行赋值时,
需要__block修饰符

Block内存管理
Block的Copy操作
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z9sQGbNX-1598110556266)(evernotecid://E87D830C-ABD9-4F44-8969-DDFD5EDD63B4/appyinxiangcom/16371682/ENResource/p299)]](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36fe7057c31043c6b30e18e433021707~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.jpg#?w=749&h=260&s=77714&e=png&b=fefefe)

为什么会有成员变量__forwarding呢?
- __forwarding持有指向该实例自身的指针,通过成员变量__forwarding访问成员变量val。这样做是为了在多个Block中使用__block变量。
- 可以实现无论__block变量配置在栈上还是堆上时都可以正确的访问__block变量。
不论在任何内存位置,都可以顺利的访问同一个__block变量。
什么时候发生copy?什么时候从栈copy到堆上?
总共有4种情况
- 调用Block 的copy实例方法时
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或GCD(Grand central dispatch)的API中传递Block时。
循环引用
_array = [NSMutableArray arrayWithObject:@"block"];
_strBlk = ^NSString *(NSString *num) {
return [NSString stringWithFormat:@"helloc_%@",_array[0]];
};
_strBlk(@"hello");
// 解决方式
_array = [NSMutableArray arrayWithObject:@"block"];
__weak NSArray * weakArray = _array;
_strBlk = ^NSString *(NSString *num) {
return [NSString stringWithFormat:@"helloc_%@",weakArray[0]];
};
_strBlk(@"hello");
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q08bQNJh-1598110556268)(evernotecid://E87D830C-ABD9-4F44-8969-DDFD5EDD63B4/appyinxiangcom/16371682/ENResource/p300)]](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eca645e8969a4bb69631ad9c26b09197~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.jpg#?w=834&h=392&s=86892&e=png&b=ffffff)
在MRC下,不会产生循环引用。
在ARC下,会产生循环引用,引起内存泄漏。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-onf8YuR6-1598110556270)(evernotecid://E87D830C-ABD9-4F44-8969-DDFD5EDD63B4/appyinxiangcom/16371682/ENResource/p302)]](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e88979e50b8046eca53c1dfd9e3dd6cf~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.jpg#?w=851&h=471&s=107823&e=png&b=ffffff)
此时如果不调用或者是长时间不调用的话,环就会一直存在。
block使用注意事项有哪些
- 循环引用 (_weak 解除循环引用,内部防止调用期间释放,__strong强引用一下 )
- 初始化问题,防止崩溃(确保初始化 使用时候要检查)
- 捕获引用时候,值捕获问题。(__block)
- 多线程情况下静态(使用锁)
避免在多线程环境下未保护的 Block 调用:如果在多线程环境中使用 Block,要注意对共享资源的访问控制。例如,如果 Block 访问并修改共享变量,可能会导致数据竞争问题。可以使用锁(如 NSLock、dispatch_semaphore 等)来保护共享资源。
循环引用造成内存泄露,常见使用检测方法