本文Demo传送门:BlockTestAppgit
【摘要】这篇文章,首先在第1节中介绍Block的定义,以及与C里面函数的对比。而后,第2节介绍实际开发中常常会用到的Block语法形式,以供读者往后查阅。只知道怎么用殊不知何时用?因此随后的第3节将介绍Block的应用场景。然而,用Block不当致使了Crash?因此,第4节有必要了解Block捕获变量的特性,以及循环引用的解决。另外,千万不要懒,一碰到Block就weak,要区分哪些不会引发循环引用。然而,若是对Block的内存机制不熟悉,也会致使Crash,因此第5节会介绍Block的内存机制。学到这里已经够用了。然而,你却想进一步了解Block的实现机制?第6节将简单介绍下clang的编译与Block的实现及其原理。github
Block:带有自动变量(局部变量)的匿名函数。它是C语言的扩充功能。之因此是拓展,是由于C语言不容许存在这样匿名函数。面试
匿名函数是指不带函数名称函数。C语言中,函数是怎样的呢?相似这样:算法
int func(int count);
复制代码
调用的时候:编程
int result = func(10);
复制代码
func就是它的函数名。也能够经过指针调用函数,看起来没用到函数名:数组
int result = (*funcptr)(10);
复制代码
实际,在赋值给函数指针时,必须经过函数的名称才能得到该函数的地址。完整的步骤应该是:bash
int (*funcptr)(int) = &func;
int result = (*funcptr)(10);
复制代码
而经过Block,就可以使用匿名函数,即不带函数名称的函数。网络
关于“带有自动变量(局部变量)”的含义,这是由于Block拥有捕获外部变量的功能。在Block中访问一个外部的局部变量,Block会持用它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的的状态。框架
捕获外部变量,看一个经典block面试题:函数
int val = 10;
void (^blk)(void) = ^{
printf("val=%d\n",val);
};
val = 2;
blk();
复制代码
上面这段代码,输出值是:val = 10,而不是2。
block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,而后在 block 块内使用该只读拷贝;换句话说block截获自动变量的瞬时值;或者block捕获的是自动变量的副本。
因为block捕获了自动变量的瞬时值,因此在执行block语法后,即便改写block中使用的自动变量的值也不会影响block执行时自动变量的值。
因此,上面的面试题的结果是10,不是2。
解决block不能修改自动变量的值,这一问题的另一个办法是使用
__block
修饰符。
__block int val = 10;
void (^blk)(void) = ^{printf("val=%d\n",val);};
val = 2;
blk();
复制代码
上面的代码,跟第一个代码段相比只是多了一个__block
修饰符。可是输出结果确是2。
约定:用法中的符号含义列举以下:
return_type
表示返回的对象/关键字等(能够是void,并省略)blockName
表示block的名称var_type
表示参数的类型(能够是void,并省略)varName
表示参数名称return_type (^blockName)(var_type) = ^return_type (var_type varName) {
// ...
};
blockName(var);
复制代码
void (^blockName)(var_type) = ^void (var_type varName) {
// ...
};
blockName(var);
复制代码
可省略写成
void (^blockName)(var_type) = ^(var_type varName) {
// ...
};
blockName(var);
复制代码
return_type (^blockName)(void) = ^return_type (void) {
// ...
};
blockName();
复制代码
可省略写成
return_type (^blockName)(void) = ^return_type {
// ...
};
blockName();
复制代码
void (^blockName)(void) = ^void (void) {
// ...
};
blockName();
复制代码
可省略写成
void (^blockName)(void) = ^{
// ...
};
blockName();
复制代码
Block实现时,等号右边就是一个匿名Block,它没有blockName,称之为匿名Block:
^return_type (var_type varName)
{
//...
};
复制代码
利用typedef
简化Block的声明:
typedef return_type (^BlockTypeName)(var_type);
复制代码
//声明
typedef void(^ClickBlock)(NSInteger index);
//block属性
@property (nonatomic, copy) ClickBlock imageClickBlock;
复制代码
//声明
typedef void (^handleBlock)();
//block做参数
- (void)requestForRefuseOrAccept:(MessageBtnType)msgBtnType messageModel:(MessageModel *)msgModel handle:(handleBlock)handle{
...
复制代码
return_type (^blockName)(var_type) = ^return_type (var_type varName) {
// ...
};
blockName(var);
复制代码
void (^globalBlockInMemory)(int number) = ^(int number){
printf("%d \n",number);
};
globalBlockInMemory(90);
复制代码
@property(nonatomic, copy)return_type (^blockName) (var_type);
复制代码
//按钮点击Block
@property (nonatomic, copy) void (^btnClickedBlock)(UIButton *sender);
复制代码
- (void)yourMethod:(return_type (^)(var_type))blockName;
复制代码
UIView+AddClickedEvent.h
- (void)addClickedBlock:(void(^)(id obj))clickedAction;
复制代码
UIView+AddClickedEvent.m
- (void)addClickedBlock:(void(^)(id obj))clickedAction{
self.clickedAction = clickedAction;
// :先判断当前是否有交互事件,若是没有的话。。。全部gesture的交互事件都会被添加进gestureRecognizers中
if (![self gestureRecognizers]) {
self.userInteractionEnabled = YES;
// :添加单击事件
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
[self addGestureRecognizer:tap];
}
}
- (void)tap{
if (self.clickedAction) {
self.clickedAction(self);
}
}
复制代码
这种形式并不经常使用,匿名Block声明后当即被调用:
^return_type (var_type varName)
{
//...
}(var);
复制代码
Block内部调用自身,递归调用是不少算法基础,特别是在没法提早预知循环终止条件的状况下。注意:因为Block内部引用了自身,这里必须使用__block
避免循环引用问题。
__block return_type (^blockName)(var_type) = [^return_type (var_type varName)
{
if (returnCondition)
{
blockName = nil;
return;
}
// ...
// 【递归调用】
blockName(varName);
} copy];
【初次调用】
blockName(varValue);
复制代码
方法的返回值是一个Block,可用于一些“工厂模式”的方法中:
- (return_type(^)(var_type))methodName
{
return ^return_type(var_type param) {
// ...
};
}
复制代码
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
NSMutableArray *children = NSMutableArray.new;
for (id attr in attribute) {
MASViewConstraint *viewConstraint = [self copy];
viewConstraint.secondViewAttribute = attr;
[children addObject:viewConstraint];
}
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self.delegate;
[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
} else {
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}
复制代码
情景:UIViewContoller有个UITableView并是它的代理,经过UITableView加载CellView。如今须要监听CellView中的某个按钮(能够经过tag值区分),并做出响应。
如上面 2.3.2节在CellView.h中@interface位置声明一个Block型的属性,为了设置激活事件调用Block,接着咱们在CellView.m中做以下设置:
// 激活事件
#pragma mark - 按钮点击事件
- (IBAction)btnClickedAction:(UIButton *)sender {
if (self.btnClickedBlock) {
self.btnClickedBlock(sender);
}
}
复制代码
随后,在ViewController.m的适当位置(- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{...
代理方法)中经过setter方法设置CellView的Block属性。Block写着当按钮被点击后要执行的逻辑。
// 响应事件
cell.btnClickedBlock = ^(UIButton *sender) {
//标记消息已读
[weakSelf requestToReadedMessageWithTag:sender.tag];
//刷新当前cell
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
};
复制代码
其实,即便Block不传递任何参数,也能够传递事件的。但这种状况,没法区分事件的激活方(cell里面的哪个按钮?)。即:
//按钮点击Block
@property (nonatomic, copy) void (^btnClickedBlock)(void);
复制代码
// 激活事件
#pragma mark - 按钮点击事件
- (IBAction)btnClickedAction:(UIButton *)sender {
if (self.btnClickedBlock) {
self.btnClickedBlock();
}
}
复制代码
// 响应事件
cell.btnClickedBlock = ^{
//标记消息已读
[weakSelf requestToReadedMessageWithTag:nil];
//刷新当前cell
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
};
复制代码
上面的响应事件,其实也是传递数据,只是它传递的对象是UIButton。以下所示,SubTableView是VC的一个属性和子视图。
SubTableView.h
@property (strong, nonatomic) void (^handleDidSelectedItem)(int indexPath);
复制代码
SubTableView.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
_handleDidSelectedItem ? _handleDidSelectedItem(indexPath) : NULL;
}
复制代码
VC.m
[_subView setHandleDidSelectedItem:^(int indexPath) {
[weakself handleLabelDidSearchTableSelectedItem:indexPath];
}];
复制代码
- (void)handleLabelDidSearchTableSelectedItem:(int )indexPath {
if (indexPath==0) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"telprompt:%@", self.searchNullView.telLabel.text]]];
}else if (indexPath==1){
[self.navigationController popViewControllerAnimated:YES];
}
}
复制代码
例如HYBNetworking网络框架中请求成功时传递接口返回数据对象的Block:
[HYBNetworking postWithUrl:kSearchProblem refreshCache:NO params:params success:^(id response) {
typeof(weakSelf) strongSelf = weakSelf;
// [KVNProgress dismiss];
NSString *stringData = [response mj_JSONString];
stringData = [DES3Util decrypt:stringData];
NSLog(@"stirngData: %@", stringData);
...
}
复制代码
链式编程思想:核心思想为将block做为方法的返回值,且返回值的类型为调用者自己,并将该方法以setter的形式返回,这样就能够实现了连续调用,即为链式编程。
Masonry的一个典型的链式编程用法以下:
[self.containerView addSubview:self.bannerView];
[self.bannerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.equalTo(self.containerView.mas_leading);
make.top.equalTo(self.containerView.mas_top);
make.trailing.equalTo(self.containerView.mas_trailing);
make.height.equalTo(@(kViewWidth(131.0)));
}];
复制代码
如今,简单使用链式编程思想实现一个简单计算器的功能:
// CaculateMaker.h
// ChainBlockTestApp
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CaculateMaker : NSObject
@property (nonatomic, assign) CGFloat result;
- (CaculateMaker *(^)(CGFloat num))add;
@end
复制代码
// CaculateMaker.m
// ChainBlockTestApp
#import "CaculateMaker.h"
@implementation CaculateMaker
- (CaculateMaker *(^)(CGFloat num))add;{
return ^CaculateMaker *(CGFloat num){
_result += num;
return self;
};
}
@end
复制代码
CaculateMaker *maker = [[CaculateMaker alloc] init];
maker.add(20).add(30);
复制代码
前面讲过block所在函数中的,捕获自动变量。可是不能修改它,否则就是“编译错误”。可是能够改变全局变量、静态变量、全局静态变量。其实这两个特色不难理解:
不能修改自动变量的值是由于:block捕获的是自动变量的const值,名字同样,不能修改
能够修改静态变量的值:静态变量属于类的,不是某一个变量。因为block内部不用调用self指针。因此block能够调用。
解决block不能修改自动变量的值,这一问题的另一个办法是使用__block
修饰符。
对于捕获ObjC对象,不一样于基本类型;Block会引发对象的引用计数变化。
@interface MyClass : NSObject {
NSObject* _instanceObj;
}
@end
@implementation MyClass
NSObject* __globalObj = nil;
- (id) init {
if (self = [super init]) {
_instanceObj = [[NSObject alloc] init];
}
return self;
}
- (void) test {
static NSObject* __staticObj = nil;
__globalObj = [[NSObject alloc] init];
__staticObj = [[NSObject alloc] init];
NSObject* localObj = [[NSObject alloc] init];
__block NSObject* blockObj = [[NSObject alloc] init];
typedef void (^MyBlock)(void) ;
MyBlock aBlock = ^{
NSLog(@"%@", __globalObj);
NSLog(@"%@", __staticObj);
NSLog(@"%@", _instanceObj);
NSLog(@"%@", localObj);
NSLog(@"%@", blockObj);
};
aBlock = [[aBlock copy] autorelease];
aBlock();
NSLog(@"%d", [__globalObj retainCount]);
NSLog(@"%d", [__staticObj retainCount]);
NSLog(@"%d", [_instanceObj retainCount]);
NSLog(@"%d", [localObj retainCount]);
NSLog(@"%d", [blockObj retainCount]);
}
@end
int main(int argc, charchar *argv[]) {
@autoreleasepool {
MyClass* obj = [[[MyClass alloc] init] autorelease];
[obj test];
return 0;
}
}
复制代码
执行结果为1 1 1 2 1。
__globalObj
和__staticObj
在内存中的位置是肯定的,因此Block copy
时不会retain对象。
_instanceObj
在Block copy
时也没有直接retain _instanceObj
对象自己,但会retain self。因此在Block中能够直接读写_instanceObj
变量。 localObj
在Block copy
时,系统自动retain
对象,增长其引用计数。 blockObj
在Block copy
时也不会retain
。
通常来讲咱们总会在设置Block以后,在合适的时间回调Block,而不但愿回调Block的时候Block已经被释放了,因此咱们须要对Block进行copy,copy到堆中,以便后用。
Block可能会致使循环引用问题,由于block在拷贝到堆上的时候,会retain其引用的外部变量,那么若是block中若是引用了他的宿主对象,那颇有可能引发循环引用,如:
- (void) dealloc {
NSLog(@"no cycle retain");
}
- (id) init {
self = [super init];
if (self) {
#if TestCycleRetainCase1
//会循环引用
self.myblock = ^{
[self doSomething];
};
#elif TestCycleRetainCase2
//会循环引用
__block TestCycleRetain * weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase3
//不会循环引用
__weak TestCycleRetain * weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#elif TestCycleRetainCase4
//不会循环引用
__unsafe_unretained TestCycleRetain * weakSelf = self;
self.myblock = ^{
[weakSelf doSomething];
};
#endif NSLog(@"myblock is %@", self.myblock);
}
return self;
}
- (void) doSomething {
NSLog(@"do Something");
}
复制代码
int main(int argc, char * argv[]) {
@autoreleasepool {
TestCycleRetain * obj = [[TestCycleRetain alloc] init];
obj = nil;
return 0;
}
}
复制代码
在上述使用 block中,虽然说使用__weak
,可是此处会有一个隐患,你不知道 self 何时会被释放,为了保证在block内不会被释放,咱们添加__strong
。更多的时候须要配合strongSelf
使用,以下:
__weak __typeof(self) weakSelf = self;
self.testBlock = ^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
[strongSelf test];
});
复制代码
在工程的TestAPP-Prefix.pch的文件中直接(不推荐)或在其导入的头文件中间接写入如下宏定义:
//----------------------强弱引用----------------------------
#ifndef weakify
#if DEBUG
#if __has_feature(objc_arc)
#define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
#endif
#else
#if __has_feature(objc_arc)
#define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
#endif
#endif
#endif
#ifndef strongify
#if DEBUG
#if __has_feature(objc_arc)
#define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
#endif
#else
#if __has_feature(objc_arc)
#define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;
#endif
#endif
#endif
复制代码
在设置Block体的时候,像以下这样使用便可。
@weakify(self);
[footerView setClickFooterBlock:^{
@strongify(self);
[self handleClickFooterActionWithSectionTag:section];
}];
复制代码
很显然答案不都是,有些状况下是能够直接使用self的,好比调用系统的方法:
[UIView animateWithDuration:0.5 animations:^{
NSLog(@"%@", self);
}];
复制代码
由于这个block存在于静态方法中,虽然block对self强引用着,可是self却不持有这个静态方法,因此彻底能够在block内部使用self。
另外,来看一个Masonry代码布局的例子,这里面的self会不会形成循环引用呢?
[self.headView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.otherView.mas_centerY);
}];
复制代码
并非 block 就必定会形成循环引用,是否是循环引用要看是否是相互持有强引用。block 里用到了 self,那 block 会保持一个 self 的引用,可是 self 并无直接或者间接持有 block,因此不会形成循环引用。能够看一下Masonry的源代码:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
复制代码
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
复制代码
持有链是这样的,并无造成引用循环:
self ->self.headView ··· MASConstraintMaker构造block->self
注意观察,这个做为方法参数的Block体并无被任何方持有。所以,咱们放心在Masonry中使用self.xxx 不会循环引用的。并且这个block里面用weakSelf还有可能会出问题,由于mas_qeual若是获得一个nil参数的话应该会致使程序崩溃。
由于UIView未强持有block,因此这个block只是个栈block,并且构不成循环引用的条件。栈block有个特性就是它执行完毕以后就出栈,出栈了就会被释放掉。看mas_makexxx的方法实现会发现这个block很快就被调用了,完事儿就出栈销毁,构不成循环引用,因此能够直接放心的使self。另外,这个与网络请求里面使用self道理是同样的。
根据Block在内存中的位置分为三种类型:
NSGlobalBlock是位于全局区的block,它是设置在程序的数据区域(.data区)中。
NSStackBlock是位于栈区,超出变量做用域,栈上的Block以及 __block变量都被销毁。
NSMallocBlock是位于堆区,在变量做用域结束时不受影响。
注意:在 ARC 开启的状况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。
正如它们名字显示得同样,代表了block的三种存储方式:栈、全局、堆。获取block对象中的isa的值,能够获得上面其中一个,下面开始说明哪一种block存储在栈、堆、全局。
生成在全局区block有两种状况:
void(^block)(void) = ^ { NSLog(@"Global Block");};
int main() {
}
复制代码
int(^block)(int count) = ^(int count) {
return count;
};
block(2);
复制代码
虽然,这个block在循环内,可是blk的地址老是不变的。说明这个block在全局段。注:针对没有捕获自动变量的block来讲,虽然用clang的rewrite-objc转化后的代码中仍显示_NSConcretStackBlock,可是实际上不是这样的。
这种状况,在非ARC下是没法编译的,在ARC下能够编译。
NSInteger i = 10;
block = ^{
NSLog(@"%ld", i);
};
block;
复制代码
设置在栈上的block,若是其做用域结束,该block就被销毁。一样的,因为__block变量也配置在栈上,若是其做用域结束,则该__block变量也会被销毁。
另外,例如
typedef void (^block_t)() ;
-(block_t)returnBlock{
__block int add=10;
return ^{
printf("add=%d\n",++add);
};
}
复制代码
堆中的block没法直接建立,其须要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block须要执行copy以后才能存放到堆中)。因为block的拷贝最终都会调用_Block_copy_internal函数。
void(^block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSInteger i = 10;
block = [^{
++i;
} copy];
++i;
block();
NSLog(@"%ld", i);
}
return 0;
}
复制代码
咱们对这个生成在栈上的block执行了copy操做,Block和__block变量均从栈复制到堆上。上面的代码,有跟没有copy
,在非ARC和ARC下一个是stack一个是Malloc。这是由于ARC下默认为Malloc(即便如此,ARC下仍是有一些例外,下面会讲)。
block在ARC和非ARC下有巨大差异。多数状况下,ARC下会默认把栈block被会直接拷贝生成到堆上。那么,何时栈上的Block会复制到堆上呢?
block在ARC和非ARC下的巨大差异
在 ARC 中,捕获外部了变量的 block 的类会是 NSMallocBlock 或者 NSStackBlock,若是 block 被赋值给了某个变量,在这个过程当中会执行 _Block_copy 将原有的 NSStackBlock 变成 NSMallocBlock;可是若是 block 没有被赋值给某个变量,那它的类型就是 NSStackBlock;没有捕获外部变量的 block 的类会是 NSGlobalBlock 即不在堆上,也不在栈上,它相似 C 语言函数同样会在代码段中。
在非 ARC 中,捕获了外部变量的 block 的类会是 NSStackBlock,放置在栈上,没有捕获外部变量的 block 时与 ARC 环境下状况相同。
例如
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self testBlockForHeapOfARC];
}
-(void)testBlockForHeapOfARC{
int val =10;
typedef void (^blk_t)(void);
blk_t block = ^{
NSLog(@"blk0:%d",val);
};
block();
}
复制代码
即便如此,ARC下仍是有一些例外:
例外
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self testBlockForHeap0];
}
#pragma mark - testBlockForHeap0 - crash
-(NSArray *)getBlockArray0{
int val =10;
return [NSArray arrayWithObjects:
^{NSLog(@"blk0:%d",val);},
^{NSLog(@"blk1:%d",val);},nil];
}
-(void)testBlockForHeap0{
NSArray *tempArr = [self getBlockArray0];
NSMutableArray *obj = [tempArr mutableCopy];
typedef void (^blk_t)(void);
blk_t block = (blk_t){[obj objectAtIndex:0]};
block();
}
复制代码
这段代码在最后一行blk()会异常,由于数组中的block是栈上的。由于val是栈上的。解决办法就是调用copy方法。这种场景,ARC也不会为你添加copy,由于ARC不肯定,采起了保守的措施:不添加copy。因此ARC下也是会异常退出。
例外的改进1
调用block 的copy函数,将block拷贝到堆上:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self testBlockForHeap1];
}
-(void)testBlockForHeap1{
NSArray *tempArr = [self getBlockArray1];
NSMutableArray *obj = [tempArr mutableCopy];
typedef void (^blk_t)(void);
blk_t block = (blk_t){[obj objectAtIndex:0]};
block();
}
-(NSArray *)getBlockArray1{
int val =10;
return [NSArray arrayWithObjects:
[^{NSLog(@"blk0:%d",val);} copy],
[^{NSLog(@"blk1:%d",val);} copy],nil];
}
复制代码
打个断点可见,该Block的类型:
例外的改进2
例以下面代码中,在addBlockToArray方法中的block仍是_NSConcreteStackBlock类型的,在testBlockForHeap2方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self testBlockForHeap2];
}
- (void)addBlockToArray:(NSMutableArray *)array {
int val =10;
[array addObjectsFromArray:@[
^{NSLog(@"blk0:%d",val);},
^{NSLog(@"blk1:%d",val);}]];
}
- (void)testBlockForHeap2{
NSMutableArray *array = [NSMutableArray array];
[self addBlockToArray:array];
typedef void (^blk_t)(void);
blk_t block = (blk_t){[array objectAtIndex:0]};
block();
}
复制代码
打个断点可见,其中Block的类型:
-(void) stackOrHeap{
__block int val =10;
blkt1 s= ^{
return ++val;};
s();
blkt1 h = [s copy];
h();
}
复制代码
无论block配置在何处,用copy方法复制都不会引发任何问题。在ARC环境下,若是不肯定是否要copy这个block,那尽管copy便可。
最后的强调,在 ARC 开启的状况下,除非上面的例外,默认只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。
为了研究编译器是如何实现 block 的,咱们须要使用 clang。clang 提供一个命令,能够将 Objetive-C 的源码改写成 c 语言的,借此能够研究 block 具体的源码实现方式。
首先cd到代码文件目录
cd /Users/ChenMan/iOSTest/BlockTestApp
复制代码
而后执行clang命令
clang -rewrite-objc main.m
复制代码
其中,main.m的代码写好以下
#include <stdio.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
typedef void (^blk_t)(void);
blk_t block = ^{
printf("Hello, World!\n");
};
block();
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
执行状况:
你会看到main.cpp
这里只选取部分关键代码。
不难看出int main(int argc, char * argv[]) {
就是主函数的实现。
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
typedef void (*blk_t)(void);
blk_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
}
复制代码
其中,__main_block_impl_0
是block的一个C++的实现(最后面的_0表明是main中的第几个block),也就是说也是一个结构体。
__main_block_impl_0
定义以下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
其中__block_impl
的定义以下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
复制代码
其结构体成员以下:
能够看出,它包含了isa指针(包含isa指针的皆为对象),也就是说block也是一个对象(runtime里面,对象和类都是用结构体表示)。
__main_block_desc_0
的定义以下:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
复制代码
其结构成员含义以下:
以上代码在定义__main_block_desc_0
结构体时,同时建立了__main_block_desc_0_DATA
,并给它赋值,以供在main函数中对__main_block_impl_0
进行初始化。
如上的main
函数中,__main_block_func_0
也是block的一个C++的实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello, World!\n");
}
复制代码
总结:综合可知
__main_block_impl_0
的 isa 指针指向了_NSConcreteStackBlock
。__main_block_impl_0
的 FuncPtr 指向了函数__main_block_func_0
。__main_block_impl_0
的 Desc 也指向了定义__main_block_desc_0
时就建立的__main_block_desc_0_DATA
,其中纪录了block结构体大小等信息。以上就是根据编译转换的结果。固然,因为 clang 改写的具体实现方式和 LLVM 不太同样,有急切底层兴趣的读者能够进行更深刻的研究。