8.1.基本概念
问题一:什么是 Block?
- Block 是将函数及其执行上下文封装起来的对象。
- 其本身其实就是函数指针。
首先看下这段代码,我们要分析 Block 具体干了些啥,可以从 Block 编译后的代码分析。使用 xcrun -sdk iphonesimulator clang -rewrite-objc BDBlock.m 可以生成 BDBlock.cpp 文件,借助这个文件可以查看 Block 内部干了些啥。
//#import "BDBlock.h"
@implementation BDBlock
- (void)myMethod
{
int multiplier = 6;
int(^MyBlock)(int) = ^int(int num) {
return num * multiplier;
};
MyBlock(2);
}
@end
截取其中一段代码
static void _I_BDBlock_myMethod(BDBlock * self, SEL _cmd) {
int multiplier = 6;
/*
int(^MyBlock)(int) = ^int(int num) {
return num * multiplier;
};
*/
int(*MyBlock)(int) = ((int (*)(int))&__BDBlock__myMethod_block_impl_0((void *)__BDBlock__myMethod_block_func_0, &__BDBlock__myMethod_block_desc_0_DATA, multiplier));
/*
MyBlock(2);
*/
((int (*)(__block_impl *, int))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock, 2);
}
问题二:Block 调用的本质是什么?
- Block 调用的本质就是函数调用。
8.2.Block 对变量的截获
问题三:请回答 Block 对下面的变量截获都有什么异同?
请回答 Block 对下面的变量截获都有什么异同?- (void)myMethod2
{
//基本数据类型的局部变量
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(@"全局变量 %d", global_var);
NSLog(@"全局静态变量 %d", global_static_var);
};
Block();
}
这段代码被编译的代码可以用命令 xcrun -sdk iphonesimulator clang -rewrite-objc -fobjc-arc BDBlock.m 来生成 cpp 文件查看。
static void _I_BDBlock_myMethod2(BDBlock * self, SEL _cmd) {
int var = 1;
__attribute__((objc_ownership(none))) id unsafe_obj = __null;
__attribute__((objc_ownership(strong))) id strong_obj = __null;
static int static_var = 3;
void(*Block)(void) = ((void (*)())&__BDBlock__myMethod2_block_impl_0((void *)__BDBlock__myMethod2_block_func_0, &__BDBlock__myMethod2_block_desc_0_DATA, var, unsafe_obj, strong_obj, &static_var, 570425344));
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
从编译后的代码可以看出
- 对于 基本数据的局部变量 Block 采用值传递的截获方式。
- 对于 对象型的局部变量 Block 采用 连同所有权修饰符 一起截获的方式
- 对于 局部静态变量 Block 以指针形式截获变量地址
- 对于 全局变量 和 静态全局变量 Block 不截获
8.3. __block 修饰符
问题四:请问下面代码能正常运行吗?是否需要 __block 关键字的协助?
NSMutableArray *array = [[NSMutableArray alloc] init];
void(^Block)(void) = ^ {
[array addObject:@"111"];
};
[array addObject:@"222"];
NSLog(@"%@",array);
Block();
NSLog(@"%@",array);
- 上述代码能正常运行
问题五:请问下面代码能正常运行吗?是否需要 __block 关键字的协助?
NSMutableArray *array = nil;
void(^Block)(void) = ^ {
array = [NSMutableArray new];
};
[array addObject:@"222"];
NSLog(@"%@",array);
Block();
NSLog(@"%@",array);
- 上述代码会编译报错,错误信息:Variable is not assignable (missing __block type specifier)
问题六:请回答什么情况下会使用到 __block 修饰符?
- 一般情况下,对被截获的变量进行 赋值 操作需添加__block 修饰符
- 请千万理解 赋值 不等于 使用 ,因此上一个问题只是使用了 array,不需要 __block 关键字的使用,能正常运行。
问题七:请回答对哪些变量在 Block 代码块里面赋值需要添加 __block 修饰词?
- 局部基本数据类型
- 局部对象数据类型
问题八:请回答对哪些变量在 Block 代码块里面赋值不需要添加 __block 修饰词?
- 全局变量
- 静态全局变量
- 静态局部变量
问题九:思考如下代码的输出?
__block int multiplier = 6;
int(^Block)(int) = ^int(int num) {
return num * multiplier;
};
multiplier = 4;
NSLog(@"Result is %d",Block(2));
- 输出 8
问题十:思考 __block 修饰基本数据类型,对基本数据类型做什么事情?
- __block 修饰基本数据变量,会把变量转变成对象。
- 并且有一个__forwarding 指针,(如果在栈上创建的)指向基本数据类型对象自己。
8.4.Block 的内存管理
问题一:思考 __block 在内存方面有哪几种类型?
- _NSConcreteGlobalBlock 全局 Block
- _NSConcreteStackBlock 栈 Block
- _NSConcreteMallocBlock 堆 Block
问题二:思考我们在对 Block 进行 copy 操作会产生什么效果?
对 Block 进行 copy 的结果问题三:思考我们在何时需要对 Block 进行 copy 操作?
栈上的 block 如果不 copy 会随着作用域的结束,而被释放问题四:思考既然栈上的__forwarding指向自己,为什么还需要呢?
- 因为栈上的 Block 经过 copy 之后,__forwarding 会指向对上的 __block 对象。
-这样就保证了,无论在任何内存位置,都可以顺利的访问同一个 __block 变量。
8.5.Block 的循环引用问题
问题一:思考下面的代码存在什么问题?如何解决?
_mutableArray = [NSMutableArray arrayWithObject:@"block"];
_blk = ^NSString*(NSString* num) {
return [NSString stringWithFormat:@"%@", _mutableArray[0]];
};
_blk(@"hello");
- 会造成循环引用,并且编译器会提示: Capturing 'self' strongly in this block is likely to lead to a retain cycle
// 解决方案
_mutableArray = [NSMutableArray arrayWithObject:@"block"];
__weak NSArray *weakArray = _mutableArray;
_blk = ^NSString*(NSString* num) {
return [NSString stringWithFormat:@"%@", weakArray[0]];
};
_blk(@"hello");
- 由于 Block 截获变量的时候,如果是对象类型的,那么会连同对象的所有权 一起截获。我们给他一个 weak 的变量,就会截获 weak 所有权的变量,从而避免了两边强引用而导致的循环引用问题。
- 这是一种自循环引用,我们通过避免自循环引用的方式解决。
问题一:思考下面的代码存在什么问题?如何解决?
__block ViewController* blockSelf = self;
_blk = ^NSString*(NSString* num) {
return [NSString stringWithFormat:@"%@", blockSelf.mutableArray[0]];
};
_blk(@"hello");
- 上述代码中,self 被 blockSelf 持有,blockSelf 被 _blk 持有,_blk 被 self 持有,这就形成了循环引用中的大环引用。
- 我们可以通过断环的方式避免循环应用。
- 但是这种方式有一个弊端,就是 block 如果不被调用,那么循环引用就会一直存在。
__block ViewController* blockSelf = self;
_blk = ^NSString*(NSString* num) {
NSString* temp = [NSString stringWithFormat:@"%@", blockSelf.mutableArray[0]];
blockSelf = nil;
return temp;
};
_blk(@"hello");
网友评论