Blocks是C語(yǔ)言的擴(kuò)充功能,也可以理解為帶有自動(dòng)變量(局部變量)的匿名函數(shù).C語(yǔ)言中可能用到的變量有自動(dòng)變量(局部變量)屠尊,函數(shù)的參數(shù)旷祸,靜態(tài)變量(靜態(tài)局部變量),靜態(tài)全局變量和全局變量.函數(shù)中能夠傳遞值的變量有靜態(tài)變量(靜態(tài)局部變量)讼昆,靜態(tài)全局變量和全局變量.
C++和Objective-C使用類可保持變量值且能夠多次持有該變量自身托享,它會(huì)聲明持有成員變量的類,由類生成的實(shí)例或?qū)ο蟊3衷摮蓡T變量的值浸赫,通過Block可以簡(jiǎn)化代碼闰围,實(shí)現(xiàn)自動(dòng)變量的保存.
基礎(chǔ)語(yǔ)法
Block的定義需要定義返回值,Block名字既峡,參數(shù)值羡榴,簡(jiǎn)單定義如下:
int (^sumBlock)(int a,int b) = ^(int a,int b) {
return a + b;
};
調(diào)用測(cè)試:
int result = sumBlock(10, 20);
printf("計(jì)算結(jié)果:%d\n",result);
通過使用typedef,函數(shù)定義變得非常容易理解.
typedef int(^SumBlock)(int a, int b);
SumBlock block2 = ^(int a,int b) {
return a + b;
};
int result2 = block2(10, 30);
block截獲變量在執(zhí)行的時(shí)候中會(huì)自動(dòng)保存下來运敢,之后的修改不會(huì)影響block中的輸出結(jié)校仑,如果想在block修改變量的值需要加入__block 修飾.
int a = 10;
int b = 20;
void (^block3)(void) = ^ {
printf("a = %d b = %d\n",a,b);
};
a = 30;
b = 40;
block3();
輸出結(jié)果:
a = 10 b = 20
截獲自動(dòng)變量的方法沒發(fā)實(shí)現(xiàn)對(duì)數(shù)組的拷貝,數(shù)組之后的更改是可以反應(yīng)到Block中的.
NSMutableArray *arr = [[NSMutableArray alloc] initWithObjects:@"1",@"2",@"3", nil];
void (^block4)(void) = ^ {
NSLog(@"數(shù)組 = %@",arr[1]);
};
arr[1] = @"20";
block4();
輸出結(jié)果:
數(shù)組 = 20
MRC與ARC
估計(jì)iOS開發(fā)學(xué)習(xí)中遇到的最多的就是Block了传惠,實(shí)際開發(fā)中Block遇到的最多的問題就是循環(huán)引用肤视,先從一份Block測(cè)試題目開始吧.下面的五道題目是同樣的Block,大家根據(jù)自己的經(jīng)驗(yàn)來判斷下面幾個(gè)題目在MRC與與ARC下執(zhí)行結(jié)果是否一樣:
題目1:
void exampleA() {
char a = 'A';
^{
printf("%cn", a);
}();
}
題目2:
void exampleB_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%cn", b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
題目3:
void exampleC_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("Cn");
}];
}
void exampleC() {
NSMutableArray *array = [NSMutableArray array];
exampleC_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
題目4:
typedef void (^dBlock)();
dBlock exampleD_getBlock() {
char d = 'D';
return ^{
printf("%cn", d);
};
}
void exampleD() {
exampleD_getBlock()();
}
題目5:
typedef void (^eBlock)();
eBlock exampleE_getBlock() {
char e = 'E';
void (^block)() = ^{
printf("%cn", e);
};
return block;
}
void exampleE() {
eBlock block = exampleE_getBlock();
block();
}
答案:
1.無論在MRC還是在ARC情況都能正確執(zhí)行.
2.ARC情況下執(zhí)行正確涉枫,MRC情況下執(zhí)行錯(cuò)誤.ARC數(shù)組添加的Block的類型是NSMallocBlock類型邢滑,MRC則將Block按照NSStackBlock處理.
3.無論是MRC還是ARC添加的Block都按照NSGlobalBlock類型處理,所以都能正確執(zhí)行.
4.只有ARC的情況正確執(zhí)行愿汰,MRC中Block在棧中創(chuàng)建的exampled_getblock返回時(shí)已經(jīng)無效,而且編譯器會(huì)報(bào)錯(cuò)困后,error: returning block that lives on the local stack.
ARC將在堆中創(chuàng)建一個(gè)autorelease的NSMallocBlock類型的Block.
5.只有ARC的情況下正確執(zhí)行,原因同上.
Block 類型
通過測(cè)試題目我們發(fā)現(xiàn)了Block有三種類NSConcreteGlobalBlock衬廷,NSConcreteStackBlock和NSConcreteMallocBlock摇予,事實(shí)上Block還有三種適用于GC的類型NSConcreteWeakBlock,NSConcreteAutoBlock和NSConcreteFinalizingBlock.
1.全局Block:如果block沒有訪問外界的任何變量或者對(duì)象吗跋,那么block就是一個(gè)全局block:
2.MRC中如果Block訪問了外界變量就會(huì)變成棧block.棧上的Block侧戴,如果其所屬的變量作用域結(jié)束宁昭,該Block就被釋放.
3.ARC為了解決棧Block釋放出現(xiàn)的問題,會(huì)通過編譯器默認(rèn)的會(huì)將棧Block進(jìn)行copy然后變成堆Block酗宋,通過引用計(jì)數(shù)管理Block积仗,延長(zhǎng)其生命周期.
以下是三個(gè)不同類型的Block展現(xiàn)形式:
typedef int (^SumBlock)(int a,int b);
@interface ViewController ()
@property (assign, nonatomic) SumBlock stackBlock;
@end
void (^globalBlock)() = ^() {
NSLog(@"FlyElephant---全局Block");
};
NSLog(@"FlyElephant---%@",globalBlock);
__weak typeof(self) weakSelf = self;
self.stackBlock = ^int(int a, int b) {
NSLog(@"FlyElephant---%@",weakSelf.stackBlock);
return a + b;
};
NSInteger test = self.stackBlock(10,10);
NSLog(@"FlyElephant---%ld---%@",test,self.stackBlock);
NSInteger num = 27;
void (^mallocBlock)() = ^() {
NSLog(@"FlyElephant---棧Block--%ld",num);
};
NSLog(@"FlyElephant---%@",mallocBlock);
友情提示:ARC項(xiàng)目中Block使用copy修飾,千萬(wàn)不要用assign修飾蜕猫,本文為演示寂曹,特意用assign對(duì)Block進(jìn)行修飾.
Block 原理
Block是對(duì)包含上下文變量的函數(shù)的封裝,是Objective-C語(yǔ)言對(duì)閉包的另外一種形式的封裝.Block同時(shí)也是一個(gè)對(duì)象回右,不過Block內(nèi)部的構(gòu)造與一般的對(duì)象差別很多.
Block的內(nèi)存布局是由六個(gè)部分組成:
1.isa 指針:所有對(duì)象都有該指針隆圆,指向Class對(duì)象.
2.flags:用于按 bit 位表示一些 block 的附加信息.
3.reserved:保留變量.
4.invoke:最重要的變量,函數(shù)指針翔烁,指向具體的 block 實(shí)現(xiàn)的函數(shù)調(diào)用地址渺氧,至少要接收一個(gè)void *型的參數(shù).
5.descriptor:指向結(jié)構(gòu)體的指針,表示該 block 的附加描述信息蹬屹,主要是 size 大小阶女,以及 copy 和 dispose 函數(shù)的指針.
6.variables:捕獲(capture)過來的變量,block 能夠訪問它外部的局部變量哩治,就是因?yàn)閷⑦@些變量(或變量的地址)復(fù)制到了結(jié)構(gòu)體中.
參考資料:
Objective-C Blocks Quiz
A look inside blocks: Episode 3 (Block_copy)