簡(jiǎn)介
Blocks是C語(yǔ)言層級(jí)語(yǔ)法和運(yùn)行時(shí)特性腐晾。 它們類似于標(biāo)準(zhǔn)C函數(shù)叉弦,但是除了可執(zhí)行代碼之外,它們還可以保存堆棧變量藻糖。 因此淹冰,塊可以保存數(shù)據(jù),在代碼執(zhí)行時(shí)使用巨柒。
1樱拴、Block可以作為函數(shù)數(shù)調(diào)用、作為函數(shù)參數(shù)洋满、作為方法參數(shù)晶乔。
2、因?yàn)楠?dú)立完整可以在多線程中使用牺勾;
3正罢、因?yàn)閾碛谢卣{(diào)時(shí)需要執(zhí)行的代碼和執(zhí)行代碼時(shí)需要的數(shù)據(jù),常常被用來(lái)實(shí)現(xiàn)回調(diào)Callback驻民。
由于Objective-C和C++都是從C派生腺怯,因此三種語(yǔ)言均可以使用(Objective-C使用更多)。iOS從4.0版本開始支持川无。在其他語(yǔ)言環(huán)境中有時(shí)被稱為“閉包”呛占。
特點(diǎn)
Block是一個(gè)匿名的內(nèi)聯(lián)代碼集合,有以下特點(diǎn):
1懦趋、像函數(shù)一樣用參數(shù)列表
2晾虑、有可推斷或者聲明的返回值類型
3、可以從定義它作用域捕獲數(shù)據(jù)
4、可選的可以修改捕獲到的數(shù)據(jù)
5帜篇、同一作用域中的其它block共享捕獲的數(shù)據(jù)
6糙捺、作用域的堆棧被銷毀后,仍然可以繼續(xù)共享定義其范圍定義的數(shù)據(jù)
由于compiler 和 runtime 保證block和其相關(guān)的數(shù)據(jù)的生命周期笙隙,因此可以copy一份傳遞到其他地方使用洪灯。
聲明和創(chuàng)建
聲明
首先說(shuō)明下函數(shù)指針的聲明格式
返回值類型 ( * 指針變量名) ([形參列表]);
Block variables保存著block的引用【固担可以像聲明函數(shù)指針一樣聲明block變量签钩,但是要把*
換成^
,例如以下均為有效的聲明
void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
Blocks支持可變的參數(shù)列表,當(dāng)沒有參數(shù)時(shí)必須寫void坏快。
通過為編譯器提供block使用的數(shù)據(jù)铅檩,傳遞參數(shù),返回值莽鸿,block設(shè)計(jì)完全類型安全昧旨。可以把block引用強(qiáng)制轉(zhuǎn)換成任意類型的指針祥得,反之亦然兔沃。但是不能使用操作符*
來(lái)訪問值,因?yàn)閎lock的值在編譯期無(wú)法計(jì)算级及。
理解:假如每次方法調(diào)用當(dāng)做一次消息發(fā)送(一般底層會(huì)有很多次)粘拾,把block中的所以消息發(fā)送按照順序放到一種能夠先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)--比方說(shuō)實(shí)現(xiàn)棧特性的結(jié)構(gòu)體;那么block變量
和^{ ... }
作用一樣都是指向結(jié)構(gòu)體的首地址创千;傳遞首地址跟其他對(duì)象指針缰雇、函數(shù)指針用法很像。
類型定義簡(jiǎn)化用法
如果經(jīng)常重復(fù)使用同一個(gè)類型的block追驴,你可以定義自己的block類型械哟,例如
//返回值類型:float 兩個(gè)參數(shù)類型:float 變量名稱:MyBlockType
typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;
創(chuàng)建
^
表示block的開始,接一個(gè)返回值類型(可選殿雪,默認(rèn)不寫)
再接一個(gè)(參數(shù)列表)
暇咆,后邊接一個(gè){代碼塊};
例如
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
解讀如下圖所示
如果沒有明確聲明返回值類型,則根據(jù)block代碼內(nèi)容推斷具體類型丙曙,例如
int multiplier = 7;
//未指明返回值類型
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
//指明返回值類型
int (^myBlock)(int) = ^ int (int num) {
return multiplier;
};
Blocks和變量
首先可以像函數(shù)一樣引用三種標(biāo)準(zhǔn)類型的變量:
1爸业、全局變量,包括靜態(tài)局部變量
2亏镰、全局函數(shù)
3扯旷、作用域內(nèi)的局部變量和參數(shù)
其次Blocks還支持另外兩種類型:
4、__block修飾的變量
5索抓、const導(dǎo)入的
所以Blocks內(nèi)部處理變量的五種情況:
1钧忽、全局變量(包括作用域內(nèi)的靜態(tài)局部變量)可以直接訪問
2毯炮、Blocks的參數(shù)可以直接訪問
3、作用域內(nèi)非靜態(tài)局部變量耸黑,作為const變量引用(只讀),強(qiáng)行修改編譯器報(bào)錯(cuò)
4桃煎、被__block修飾的作用域內(nèi)非靜態(tài)局部變量,可以直接訪問
5大刊、Blocks內(nèi)部聲明的局部變量为迈,可以直接訪問
示例代碼如下
int global_var = 1;
- (void)viewDidLoad {
[super viewDidLoad];
static int static_var = 2;
__block int loacal_var = 3;
__block int const_var = 3;
void (^myBlock)(int) = ^(int number) {
global_var = global_var * 10;
static_var = static_var * 10;
loacal_var = loacal_var * 10;
number = number * 10;
NSLog(@"局部變量:%d ,說(shuō)明局部變量可以讀取",const_var);
NSLog(@"參數(shù)變量原:4 修改后%d缺菌,說(shuō)明可以正常訪問",number);
};
myBlock(4);
NSLog(@"全局變量原:1 修改后:%d葫辐,說(shuō)明可以正常訪問 ",global_var);
NSLog(@"靜態(tài)變量原:2 修改后:%d,說(shuō)明可以正常訪問 ",static_var);
NSLog(@"__block修飾的局部變量原:3 修改后:%d男翰,說(shuō)明可以正常訪問 ",loacal_var);
}
//輸出結(jié)果
局部變量:3 另患,說(shuō)明局部變量可以讀取
參數(shù)變量原:4 修改后40纽乱,說(shuō)明可以正常訪問
全局變量原:1 修改后:10蛾绎,說(shuō)明可以正常訪問
靜態(tài)變量原:2 修改后:20,說(shuō)明可以正常訪問
__block修飾的局部變量原:3 修改后:30鸦列,說(shuō)明可以正常訪問
Blocks和對(duì)象
對(duì)象通過Properties來(lái)引用Blocks租冠。語(yǔ)法和定義Blocks語(yǔ)法類似;例如
@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end
self.blockProperty = ^{
...
};
self.blockProperty();
注意:此處應(yīng)該用copy薯嗤,因?yàn)閎lock需要被copy來(lái)保存引用的外界的變量顽爹,無(wú)需關(guān)心引用計(jì)數(shù)問題,編譯器自動(dòng)進(jìn)行管理骆姐。
還可以通過自定義類型簡(jiǎn)化
typedef void (^XYZSimpleBlock)(void);
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end
避免循環(huán)引用
當(dāng)變量變成實(shí)例變量的時(shí)候镜粤,需要避免循環(huán)引用的問題。由于Blocks會(huì)持有使用的變量玻褪,比如在Viewcontroller中肉渴,block作為一個(gè)property被self持有,block中使用self調(diào)用方法带射,這樣便造成循環(huán)引用的問題使得二者在不使用時(shí)均得不到釋放同规。解決方法是在block中使用self的弱引用,用__weak修飾窟社。
@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // capture the weak reference
// to avoid the reference cycle
}
}
用法
作為變量
如果聲明Block作為一個(gè)變量券勺,可以像函數(shù)一樣調(diào)用。例如
int (^oneFrom)(int) = ^(int anInt) {
return anInt - 1;
};
printf("1 from 10 is %d", oneFrom(10));
// Prints "1 from 10 is 9"
float (^distanceTraveled)(float, float, float) =
^(float startingSpeed, float acceleration, float time) {
float distance = (startingSpeed * time) + (0.5 * acceleration * time * time);
return distance;
};
float howFar = distanceTraveled(0.0, 9.8, 1.0);
// howFar = 4.9
作為函數(shù)參數(shù)
可以像傳遞其它參數(shù)一樣傳遞block灿里。大多數(shù)的情況不需要聲明关炼,只需要作為參數(shù)以內(nèi)聯(lián)的方式實(shí)現(xiàn)。例如
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
//根據(jù)首字符進(jìn)行一次排序
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
char *left = *(char **)l;
char *right = *(char **)r;
//此函數(shù)表示:以參數(shù)3的長(zhǎng)度計(jì)算前2個(gè)參數(shù)的差值
return strncmp(left, right, 1);
});
// Block implementation ends at "}"
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
作為方法參數(shù)
Cocoa Touch 提供了很多方法使用Blocks匣吊〉涟牵可以傳遞block作為參數(shù)使用跪楞。例如
//是否包含指定字符串
__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";
[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
*stop = YES;
found = YES;
}
}];
// At this point, found == YES
作用
很大程度上起到了簡(jiǎn)化的作用。
簡(jiǎn)化回調(diào)
Blocks可以用處替換傳統(tǒng)回調(diào)的原因如下:
1侣灶、方法調(diào)用和回調(diào)代碼集中在一起甸祭;還常作為framework methods的參數(shù)。
2褥影、可以直接訪問局部變量池户。
簡(jiǎn)化枚舉
對(duì)一個(gè)數(shù)組進(jìn)行枚舉,常用的方法有3種
1凡怎、for循環(huán)
2校焦、for的泛型遍歷for (type *object in collection)
3、使用帶有block的簡(jiǎn)化方法统倒。例如- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];
//for循環(huán)
for (int i = 0; i < array.count; i ++)
{
NSString *item = array[i];
NSLog(@"index:%d value:%@",i,item);
}
//泛型遍歷
for (NSString *item in array)
{
NSLog(@"value:%@",item);
}
//枚舉方法
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"index:%ld value:%@",idx,obj);
}];
簡(jiǎn)化并發(fā)任務(wù)
每個(gè)block都是獨(dú)立的工作單元寨典,包含可執(zhí)行代碼和保存使用的數(shù)據(jù),這使得它能夠在多線程開發(fā)中被異步調(diào)用房匆。
系統(tǒng)提供了一系列的多線程編程技術(shù)耸成,其中包括兩種任務(wù)調(diào)度機(jī)制:Operation queues 和 Grand Central Dispatch(GCD)。不像Thread關(guān)注怎么樣管理運(yùn)行浴鸿,把需要執(zhí)行的任務(wù)打包到blocks中井氢,然添加到隊(duì)列中,然后交給系統(tǒng)根據(jù)當(dāng)時(shí)的資源自動(dòng)執(zhí)行岳链。
簡(jiǎn)化Operation Queues
//定義任務(wù)
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
...
}];
// 主線程執(zhí)行
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
// 后臺(tái)執(zhí)行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
更多知識(shí)請(qǐng)閱讀Operation Queues
簡(jiǎn)化Grand Central Dispatch
//獲取并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//定義任務(wù)添加到隊(duì)列并執(zhí)行
dispatch_async(queue, ^{
NSLog(@"Block for asynchronous execution");
});
更多知識(shí)請(qǐng)閱讀 Dispatch Queues
參考文獻(xiàn):Blocks Programming Topics花竞、Working with Blocks