前言
最近,我的前同事问了我一个问题,你知道
(global、malloc、stack)block
吗,什么是(global、malloc、stack)block
,相信你在面试中或多或少遇到过一些Blocks的题目,一些简单,一些偏门,但是我认为脱离实际开发的面试题对于面试应聘者是完全无意义的,或许你知道Blocks
的内存结构,你也知道Blocks
的isa
在不同情况下指向的类,但这些仅适合拿来茶余饭后交谈的话题(装B
)罢了,因为你在半年一年之后自己都不清楚它是什么,只是再翻出来能很快顿悟,所以我不建议面试官将这些放在台面上讨论。
那么这篇文章写的将是实际开发所需要用到的,同时也是Apple官方
给出的Blocks
文档
关于Block
对象的内存结构我不打算在本文讲解,你可以在Block
实现的源码中去了解,但是从源码中可以论证一点 Block
的isa
指针指向的类包含了一下几种:
_NSConcreteStackBlock
_NSConcreteMallocBlock
_NSConcreteAutoBlock
_NSConcreteFinalizingBlock
_NSConcreteGlobalBlock
_NSConcreteWeakBlockVariable
并不是很多博客上的文章及面试官口中所说的这三种:
_NSConcreteStackBlock
_NSConcreteMallocBlock
_NSConcreteGlobalBlock
本文目录
一.Blocks概念、概述
二.Blocks申明、创建、使用
三.Blocks的变量类型(循环引用关系)、__block储存类型(本文重点)
一.Blocks概念、概述
闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。Block
实际上就是 Objective-C
语言对于闭包的实现。通常来说,Block
都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调
Block
代表通常是小的,独立的代码片段。因此,它们特别有用,作为可以并发执行的工作单元或者集合中的项目封装的工具,或者当另一个操作完成时作为回调。
Block
是传统回调函数的有用替代,主要有两个原因:
- 它们允许您在方法实现的上下文中执行的调用点编写代码,因此,
Block
通常是框架方法的参数。 - 它们允许访问局部变量,而不是使用需要一个体现您执行操作所需的所有上下文信息的数据结构的回调函数,您只需直接访问局部变量即可。
对于官方文档上的这两点有点绕口,理解为,可以在方法的实现中调用Block
,同时可以访问局部变量,将这些信息回调到外部结构中去。
二.Blocks申明、创建、使用
2.1声明、创建
int multiplier = 7;
int (^ myBlock)(int) = ^(int num){
return num * multiplier;
};
下例说明了该示例:
blocks.jpg
注意:
int (^ myBlock)(int)
表示申明了一个名为myBlock
的Block指针来保存 ^(int num){return num * multiplier;}
对象,如果你对这句话不了解,你可以去看看我的 iOS 程序(APP)运行中的内存分配 这遍文章。
此处你可理解为 UIView *view = [UIView new];
中的 UIView *view
为指向UIView类型的指针,而[UIView new]
是生成的一个UIView对象。
此处将代码分成两部分:
-
int (^ myBlock)(int)
Block的声明(可比着UIView *view
看) -
^(int num){return num * multiplier;}
Block的实现(可比着[UIView new]
看)
2.2 Block使用
显而易见,有了上面的基础,我们就能够大胆的猜测Block的用法了。
- 1.声明全局的Block。
- 2.作为局部变量使用。
- 3.用作属性或者成员变量。
- 4.作为函数的参数传递。
1.声明全局的Block:
#import "ViewController.h"
/** 全局初始化Block */
void(^kGlobalBlock)(id , id ) = ^(id mayActionType, id parameters){
NSLog(@"%@,%@",mayActionType,parameters);
};
/** 全局未初始化Block */
void(^kGlobalBlockWaitingInitinalize)(id , id );
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self golbalBlock];
}
#pragma mark - - 全局类型的 Block
- (void)golbalBlock{
kGlobalBlock(@1,@"Bob");
kGlobalBlockWaitingInitinalize = ^(id parameterOne, id parameterTwo){
NSLog(@"parameterOne:%@ \n parameterOne:%@",parameterOne,parameterTwo);
};
kGlobalBlockWaitingInitinalize(@"Alice",@"Lucy");
}
@end
注意,调用Block时必须保证Block已经被初始化,因为此处为Block都在内部实现,Block都是已经初始化。 如果调用了一个未被初始化的Block ,程序将会崩溃。
两种验证Block是否初始化的方法
if (kGlobalBlockWaitingInitinalize) {
kGlobalBlockWaitingInitinalize(@"Alice",@"Lucy");
}
!kGlobalBlockWaitingInitinalize ? : kGlobalBlockWaitingInitinalize(@"Alice",@"Lucy");
2.作为局部变量使用:
局部变量即在方法、函数中的Block
- (void)viewDidLoad {
[super viewDidLoad];
int multiplier = 7;
int (^ myBlock)(int) = ^(int num){
return num * multiplier;
};
myBlock(3);
}
3.用作属性或者成员变量:
#import "ViewController.h"
typedef NSString * (^BlocksTypeParameter)(id , id);
typedef NSString * (^BlocksTypeNonParameter)();
typedef void (^BlocksTypeNonReture)(id parameterOne, id parameterTwo);
@interface ViewController ()
/* 无参数类型 */
@property (nonatomic, copy) NSString *(^blockProperty)();
/* 有参数类型 */
@property (nonatomic, copy) NSString *(^blockPropertyWithParameters)(NSString *);
/* 无返回值类型 */
@property (nonatomic, copy) void(^blockPropertyWithNonRetern)();
@end
@implementation ViewController{
/* 有参数类型 */
BlocksTypeParameter _parameterBlock;
/* 无参数类型 */
BlocksTypeNonParameter _nonParemeterBlock;
/* 无返回值类型 */
BlocksTypeNonReture _nonRetureBlock ;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self propertyBlock];
}
#pragma mark - - 属性、成员变量类型的 Block
- (void)propertyBlock{
NSString *name = @"Bob";
// 无参数类型
_blockProperty = ^{
NSLog(@"no parameters");
return name;
};
// 有参数类型
_blockPropertyWithParameters = ^(NSString *parameter){
NSLog(@"has parameters");
return parameter;
};
// 无返回值类型
_blockPropertyWithNonRetern = ^{
NSLog(@"non retern");
};
}
4.作为函数的参数传递
- (void)viewDidLoad {
[super viewDidLoad];
[self postNetWorkWithParameters:@"parameters" success:^(id response) {
NSLog(@"%@",response);
} failure:^(id error) {
NSLog(@"%@",error);
}];
}
- (void)postNetWorkWithParameters:(id)parameters
success:(void (^)(id response))finishBlock
failure:(void (^)(id error))failure{
finishBlock(@"success");
failure(@"failure");
}
三.Blocks的变量类型(循环引用关系)、__block储存类型(本文重点)
如果你还在为使用Block照成循环引用而感到苦恼,那么看完这里将会解开你的疑惑
首先来说说官方文档上关于Block可使用的变量归为5类
1.Global variables are accessible, including static variables that exist within the enclosing lexical scope.
2.Parameters passed to the block are accessible (just like parameters to a function).
3.Stack (non-static) variables local to the enclosing lexical scope are captured as const variables. Their values are taken at the point of the block expression within the program. In nested blocks, the value is captured from the nearest enclosing scope.
4.Variables local to the enclosing lexical scope declared with the __block storage modifier are provided by reference and so are mutable. Any changes are reflected in the enclosing lexical scope, including any other blocks defined within the same enclosing lexical scope. These are discussed in more detail in
The __block Storage Type.
5.Local variables declared within the lexical scope of the block, which behave exactly like local variables in a function. Each invocation of the block provides a new copy of that variable. These variables can in turn be used as const or by-reference variables in blocks enclosed within the block.
即:
1.全局变量
2.传递给Block的参数
3.方法中局部变量非__block 捕获变量
4.方法中__block 语法修饰的变量
5.Block内声明的变量
3.1 关于__block存储类型:
您可以通过应用__block存储类型修饰符来指定导入的变量是可变的,即读写。
__block变量存储在变量的作用域范围中,以及在变量的作用域中声明或创建的所有Block
和Block Copy
之间共享。因此,如果在框架中声明的Block的Copies of the blocks
都能在框架的末尾(例如,通过在某个地方排队等待执行),那么存储将会在堆栈的销毁中存活下来。在给定作用域范围内的多个Block可以同时使用一个共享变量。
作为优化,Block存储从堆栈开始,就像Block本身一样。如果这个Block是用block copy
复制的,变量会被复制到堆中。因此,__block
变量的地址可以随时间变化。
对于__block
变量还有两个进一步的限制:它们不能是variable length arrays
,也不能是包含C99 variable length arrays
的结构。
3.2 关于循环引用
如果您在方法的实现中使用了一个Block,当这个Block 被复制时,它会对Block中使用的对象的进行强引用:
那么关于循环引用的造成的原因必然是,在Block Copy
到堆中时Block强引用了这个对象,然后对象又存在强引用Block的情况下就会造成强引用。
不要在所有的Block中都将对象 __weak 或者__weak再__strong、
不要在所有的Block中都将对象 __weak 或者__weak再__strong、
不要在所有的Block中都将对象 __weak 或者__weak再__strong、
重要的事情说三遍。
第一种情况:
在block中使用对象,判断是否造成循环引用,需要判断当前对象是否强引用了block,block是否也强引用了改对象。
第二种情况:
对象A-强引用-对象B,对象B强引用Block,Block中又持有了对象A,也是一种循环引用。 对象A需要Block释放后才能释放,block需要对象B释放后才能释放,对象B需要对象A释放后才能释放,所以三者形成了循环引用。
以下是示例代码
@interface BlockView : UIView
@property (nonatomic, copy) void(^myBlcok)() ;
@end
@implementation BlockView
@end
@interface ViewController ()
@property (nonatomic, copy) NSString *(^blockProperty)();
@property (nonatomic, strong) BlockView *blockView;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// case 1
_blockProperty = ^{
return self.name;
};
_blockView.myBlcok = ^{
NSLog(@"%@",_blockView); // 关注点不只在self,而是谁持有这个Block。
};
// case 2
_blockView.myBlcok = ^{
NSLog(@"%@",self.name);
};
}
B387E9BA-9F1E-47F3-9FEE-238499E9A8CD.png
第一种是在两者之间循环,第二种是在三者之间循环。
以下是对循环引用的解决方案:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
};
那么对于方法、函数中的Block,大部分只存在Block对 对象进行强引用,不存在对象对Block强引用,此时的Block作为参数先是保存在栈中,然后 被Copy到堆中。
但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:(此部分参考《招聘一个靠谱的iOS》面试题参考答案(下))
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakSelf;
[strongSelf dismissModalViewControllerAnimated:YES];
}];
self --> _observer --> block --> self 显然这也是一个循环引用。
网友评论