本文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 不太同樣,有急切底層興趣的讀者能夠進行更深刻的研究。