block
- Block介绍
- 截获变量
- __block修饰符
- Block的内存管理
- Block的循环引用
什么是Block?
Block是将函数及其执行上下文封装起来的对象。
//想要更彻底的理解block需要clang它的中间码
clang -rewrite-objc file.m 查看编译之后的文件内容
- Block是一个对象
- 对象封装了函数和执行上下文
block的调用
Block调用就是函数的调用
截获变量
下述代码的结果是16,因为block对局部变量multiplier进行了截获
int multiplier = 8;
int(^MuBlock)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 6
MuBlock(2);
- 局部变量
- 基本数据类型
- 对象类型
- 静态局部变量
- 全局变量
- 静态全局变量
什么是block截获变量?
- 基本数据类型的局部变量截获其值
- 对于对象类型的局部变量联通所有权修饰符一起截获
- 以指针形式截获局部静态变量
- 不截获全局变量、静态全局变量
如何理解我们需要编译中间码来帮我理解
//因为在ARC中的block跟MRC不一样
//因为MRC中没有block循环引用问题,下面会有解释
clang -rewrite-objc -fobjc-arc file.m
编译代码,以及OC代码如下:
struct __MyBlock__MyMethod_block_impl_0 {
struct __block_impl impl;
struct __MyBlock__MyMethod_block_desc_0* Desc;
//截获局部变量的值(基本数据类型)
int var;
//连同所有权修饰符一起截获
__unsafe_unretained id unsafe_obj;
__strong id strong_obj;
//以指针形式截获静态局部变量
int *static_var;
//对全局变量、静态全局变量不截获
__MyBlock__MyMethod_block_impl_0(void *fp, struct __MyBlock__MyMethod_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;
}
};
//全局变量
int global_var = 6;
//静态全局变量
static int static_global_var = 8;
- (void)MyMethod{
//基本数据类型的变量
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 对象类型> unsafe_obj %@",unsafe_obj);
NSLog(@"局部变量<__strong 对象类型> strong_obj %@",strong_obj);
NSLog(@"静态局部变量 static_var %d",static_var);
NSLog(@"全局变量 global_var %d",global_var);
NSLog(@"静态全局变量 static_global_var %d",static_global_var);
};
Block();
}
截获变量的总结
- 局部变量
- 基本数据类型
- 截获其值
- 对象类型
- 指针连同所有权一起截获
- 基本数据类型
- 静态局部变量
- 截获其指针
- 全局变量
- 不截获
- 静态全局变量
- 不截获
__block修饰符
什么场景需要使用__block?
一般情况下,对截获变量进行赋值(跟使用操作不一样)操作需添加__block修饰符。
例子:下面代码有什么问题吗?
- (void)test{
NSMutableArray *array = [NSMutableArray array];
void(^Block)(void) = ^{
[array addObject:@21];
};
Block();
}
答案:没有问题,因为array并没有去赋值。不需要使用__block。
再来看一个例子:下面代码有什么问题吗?
- (void)test{
NSMutableArray *array = nil;
void(^Block)(void) = ^{
array = [NSMutableArray array];
};
Block();
}
答案: array需要添加__block修饰符。
对变量进行赋值时,合适需要添加__block?
- 需要添加__block修饰符的情况?
- 局部变量
- 基本数据类型
- 对象类型
- 局部变量
- 不需要添加__block修饰符
- 静态局部变量
- 全局变量
- 静态全局变量
__block 修饰的变量最终变成了什么?
还是通过clang 命令来编译出中间码
- (void)MyMethod{
__block int multiplier = 8;
int(^MuBlock)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 6;
NSLog(@"%d",MuBlock(2));
}
// @implementation MyBlock
struct __Block_byref_multiplier_0 {
//因为有isa指针
void *__isa;
//指向同类指针的__forwarding
__Block_byref_multiplier_0 *__forwarding;
int __flags;
int __size;
//我们截获的变量
int multiplier;
};
综合上述说明通过__block修饰的变量最终变成了对象,如果在OC代码中直接修改这个变量的值,就会走下面的步骤
static void _I_MyBlock_MyMethod(MyBlock * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_multiplier_0 multiplier = {(void*)0,(__Block_byref_multiplier_0 *)&multiplier, 0, sizeof(__Block_byref_multiplier_0), 8};
int(*MuBlock)(int) = ((int (*)(int))&__MyBlock__MyMethod_block_impl_0((void *)__MyBlock__MyMethod_block_func_0, &__MyBlock__MyMethod_block_desc_0_DATA, (__Block_byref_multiplier_0 *)&multiplier, 570425344));
//打印代码之前,我们给这个__block修饰的变量对象的值赋值到了6
(multiplier.__forwarding->multiplier) = 6;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_8z_j2rlh3t16m9cdrljdz_r67380000gn_T_Myblock_5bd0fd_mi_0,((int (*)(__block_impl *, int))((__block_impl *)MuBlock)->FuncPtr)((__block_impl *)MuBlock, 2));
}
//block方法实现
static int __MyBlock__MyMethod_block_func_0(struct __MyBlock__MyMethod_block_impl_0 *__cself, int num) {
__Block_byref_multiplier_0 *multiplier = __cself->multiplier; // bound by ref
return num * (multiplier->__forwarding->multiplier);
}
打印代码之前,给这个__block修饰的变量对象的值赋值到了6,并且在block中的实现是直接通过(multiplier->__forwarding->multiplier)来进行操作,所以打印结果应该会是修改过后的值。也就是12.
__forwarding存在的意义?
- 不论在任何内存位置,都可以访问同一个__block变量。
__block相关的笔试题
- (void)MyMethod{
__block int multiplier = 8;
int(^MuBlock)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 6;
NSLog(@"%d",MuBlock(2));
}
上述调用结果是12.
Block的内存管理
- _NSConcreteGlobalBlock 全局block
- _NSConcreteStackBlock 栈block
- _NSConcreteMallocBlock 堆block
Block的内粗分布:
Block的copy操作
声明一个成员变量是block,如果这个block使用的是assign,通过成员变量去访问block的话,可能栈所对应的函数退出之后,在内存中就销毁掉了,所以继续访问可能崩溃
栈上Block的copy
当我们在栈上面的block进行copy以后,那么在MRC环境下是否会引起内存泄漏?
产生内存泄漏。
栈上的Block的销毁
栈上,等作用域
栈上__block变量的copy
__block修饰的变量都会转换成一个对象,而这个对象被copy的时候会在堆上面开辟一块空间生成一个全新的__block修饰的变量对象,而这两个对象的__forwarding指针都指向了堆上面的对象。如果没有做copy操作那么就使用的是栈上面的__block对象。
Block代码的解读
@property(nonatomic,copy)MuBlock blk;
- (void)MyMethod{
//用__block修饰的变量会在栈上生成一个block对象,里面有__forwarding指针和成员变量multiplier。__forwarding指针指向这个block对象自己
__block int multiplier = 8;
//当我向_blk属性赋值的时候实际上是对这个block进行copy,所以这个block会在堆上开辟一个空间。在堆上有另外一个副本
_blk = ^int(int num){
return num * multiplier;
};
/这个时候修改的值是__forwarding指向的对象的成员变量multiplier,因为_blk实际上是堆上面的block,下面的代码实际上是修改堆上面的__block对象
multiplier = 6;
[self excMuBlock];
}
- (void)excMuBlock{
int result = _blk(4);
NSLog(@"%d",result);
结果应该是24.
}
代码分析题
block循环引用的问题
下面代码有何问题?
//在ARC环境下
- (void)MyMethod{
//默认是strong属性
_array = [NSMutableArray arrayWithObjects:@"111", nil];
//默认是copy属性
//_array、 _strblk都是当前属性的_array是strong,_strblk是copy
_strblk = ^NSString *(NSString * str){
return [NSString stringWithFormat:@"%@",_array[0]];
};
_strblk(@"hello");
}
下面继续分析clang代码
static void _I_MyBlock_MyMethod(MyBlock * self, SEL _cmd) {
(*(NSMutableArray *__strong *)((char *)self + OBJC_IVAR_$_MyBlock$_array)) = ((NSMutableArray * _Nonnull (*)(id, SEL, ObjectType _Nonnull __strong, ...))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("arrayWithObjects:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_8z_j2rlh3t16m9cdrljdz_r67380000gn_T_Myblock_e9f218_mi_0, __null);
(*(__strong xhBlock *)((char *)self + OBJC_IVAR_$_MyBlock$_strblk)) = ((NSString *(*)(NSString *__strong))&__MyBlock__MyMethod_block_impl_0((void *)__MyBlock__MyMethod_block_func_0, &__MyBlock__MyMethod_block_desc_0_DATA, self, 570425344));
((NSString *(*)(__block_impl *, NSString *__strong))((__block_impl *)(*(__strong xhBlock *)((char *)self + OBJC_IVAR_$_MyBlock$_strblk)))->FuncPtr)((__block_impl *)(*(__strong xhBlock *)((char *)self + OBJC_IVAR_$_MyBlock$_strblk)), (NSString *)&__NSConstantStringImpl__var_folders_8z_j2rlh3t16m9cdrljdz_r67380000gn_T_Myblock_e9f218_mi_2);
}
由中间码我们可以分析出来,当在block中使用_array的时候实际上是通过(*(NSMutableArray *__strong *)((char *)self + OBJC_IVAR_$_MyBlock$_array))
这样的形式获取到_array,这样的话,相当于,在self中的block中又强引用了self这样就是循环引用。自循环引用
解决方案:
//第一种
- (void)MyMethod{
_array = [NSMutableArray arrayWithObjects:@"111", nil];
__weak NSArray *weakarray = _array;
_strblk = ^NSString *(NSString * str){
return [NSString stringWithFormat:@"%@",weakarray[0]];
};
_strblk(@"hello");
}
//第二种
- (void)MyMethod{
_array = [NSMutableArray arrayWithObjects:@"111", nil];
__weak typeof(self)weakself = self;
_strblk = ^NSString *(NSString * str){
return [NSString stringWithFormat:@"%@", weakself.array[0]];
};
_strblk(@"hello");
}
创建一个弱引用指针指向_array,这样在block中,使用的就是弱引用指针,当前对象释放后,弱引用也就会自动释放,当前当前对象的block也会被释放掉。
分析下面代码:
- (void)MyMethod{
__block MyBlock *blockself = self;
_blk = ^int(int num){
return num * blockself.var;
};
_blk(3);
}
分析
- MRC下,不会产生循环引用
- 在ARC下会产生循环引用,引起内存泄漏
在self修饰为__block的时候,系统会自动生成一个对象__Block_byref_blockself_0
而这个对象强持有self。最终导致了,当前对象持有block,block持有__block对象,__block对象对象持有self造成大环引用。
ARC下的解决方案
断环,当我block中的__block使用完成以后,需要直接赋值为nil这样就可以断环了。
- (void)MyMethod{
__block MyBlock *blockself = self;
_blk = ^int(int num){
int = num * blockself.var;
blockself = nil;
return num * blockself.var;
};
_blk(3);
}
BLock面试题
什么是block?
block就是一个对象,这个对象封装了函数以及其上下文。
为什么block会产生循环引用?
如果当前block对当前对象的成员变量有一个强引用,对象的成员变量归根究底的还是需要通过self去访问它,那么就是当前对象强引用了blcok,而block中又调用了self并将其强引用,这样就造成了自循环引用,解决方案使用__weak修饰你的成员变量,或者修饰当前对象。在block直接调用weak变量,
大环引用,当使用__block修饰self,并且self强引用了block。这样就造成了大环引用。
self强持有block,block强持有__block对象,__block对象强持有了self,这就是大环循环引用。解决方案就是使用断环的方式,但是有一个缺点就是,如果这个block不去执行的话永远不会解除这个循环引用。将__block修饰的self在block中使用完成置nil。
怎么样理解Block截获变量的特性?
- 基本数据局部变量
- 值
- 对象类型局部变量
- 对其持有一个强引用,连同它的所有权
- 对于静态局部变量
- 对其指针进行截获
- 静态全局变量、全局变量
- 不产生截获的
你都遇到过哪些循环引用?你又是如何解决的?
block捕获的变量是当前的成员变量,block也是当前对象的成员变量,就会造成自循环引用,可以避免这种情况,对当前当前成员变量使用__weak所有权修饰符去解除
__block的情况也会造成循环引用,这种情况跟自循环引用不太一样,大环循环引用有个弊端就是当你在不调用block的情况下,这个循环引用会一直解不开。