Block對象是 C 級別的語法和運行時特性。它們和標准 C 函數很類似,但是除了可執行代碼外,它們還可能包含了變量自動綁定(棧)或內存托管(堆)。所以一個block維護一個狀態集(數據),它們可以在執行的時候用來影響程序行為。
你可以用 blocks來編寫函數表達式,這些表達式可以作為 API 使用,或可選的存儲,或被多個線程使用。Blocks作為回調特別有用,因為block攜帶了進行回調所需要的執行代碼和執行過程中需要的數據。
Blocks在GCC和Clang裡面可用,它附帶在 Mac OS X v10.6 裡面的Xcode開發工具裡面。你可以在 Mac OS X v10.6 及其之後,和 iOS 4.0 及其之後上面使用blocks。Blocks運行時是開源的,你可以在 LLVM’s compiler-rt subproject repository(LLVM 的 RT 編譯器的子項目庫)裡面找到它。Blocks同樣作為標准 C 工作組出現在N1370:Apple’s Extensions to C(該文檔同樣包括了垃圾回收機制)。因為Objective-C和C++都是從 C 發展而來,blocks被設計在三種語言上面使用(也包括Objective-C++)。(語法反應了這一目標)
你應該閱讀該文檔來掌握 block 對象是什麼和如何在C,C++或Objective-C上面使用它們來讓你的程序更高效和更易於維護。
本文檔結構
該文檔包含了以下幾個章節:
第一章“Blocks入門”提供了一個對blocks的快速的和實際的介紹。
第二章“概念概述”提供了對blocks概念的介紹。
第三章“聲明和創建Blocks”描述如何聲明block變量,並實現該blocks。
第四章“Blocks和變量”介紹了blocks和變量的交互,並定義__block存儲類型修飾
符。
第五章“使用Blocks”說明不同的使用模式。
第一章 Blocks入門以下部分使用實際的例子幫助你開始使用Blocks。
1.1聲明和使用一個Block
使用^操作符來來聲明一個block變量和指示block文本的開始。Block本身的主
體被{}包含著,如下面的例子那樣(通常使用 C 的;符合指示block的結束):
int multiplier =7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
注意 block可以使用相同作用域范圍內定義的變量。
如果你聲明一個 block 作為變量,你可以把它簡單的作為一個函數使用:
int multiplier =7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
printf("%d", myBlock(3));
// prints "21"
1.2直接使用Block
在很多情況下,你不需要聲明一個 block 變量;相反你可以簡單的寫一個內聯(inline)的block文本,它需要作為一個參數使用。以下的代碼使用qsort_b函數。qsort_b和標准qsort_r函數類似,但是它使用block作為最後一個參數。
char *myCharacters[3] = {"TomJohn","George","Charles Condomine" };
qsort_b(myCharacters,3,sizeof(char *), ^(constvoid *l,constvoid *r) {
char *left = *(char **)l;
char *right = *(char **)r;
returnstrncmp(left, right,1);
});
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
1.3 Cocoa的Blocks
在 Cocoa frameworks裡面有部分方法使用block作為參數,通常不是執行一個對象的集合操作,就是在操作完成的時候作為回調使用。下面的例子顯示了如何通過NSArray的方法sortedArrayUsingComparator:使用block。該方法使用一個參數,即block。為了舉例說明,該情況下block被定義為NSComparator的局部變量:
NSArray *stringsArray = [NSArrayarrayWithObjects:
@"string 1",
@"String 21",
@"string 12",
@"String 11",
@"String 02",nil];
staticNSStringCompareOptions comparisonOptions =NSCaseInsensitiveSearch |NSNumericSearch |
NSWidthInsensitiveSearch |NSForcedOrderingSearch;
NSComparator finderSortBlock = ^(id string1,id string2) {
NSRange string1Range =NSMakeRange(0, [string1length]);
return [string1 compare:string2 options:comparisonOptions range:string1Range locale:currentLocale];
};
NSArray *finderSortArray = [stringsArraysortedArrayUsingComparator:finderSortBlock];
NSLog(@"finderSortArray: %@", finderSortArray);
/*
Output:
finderSortArray: (
"string 1",
"String 02",
"String 11",
"string 12",
"String 21"
)*/
1.4 __block變量
Blocks的最大一個特色就是可以修改相同作用域的變量。你可以使用__block存儲類型修飾符來給出信號要修改一個變量。改編“Cocoa的Blocks”所示的例子,你可以使用一個block來計數多少個字符串和block中只讀變量currentLocal相同:
NSArray *stringsArray = [NSArray arrayWithObjects:
@"string 1",
@"String 21",// <-
@"string 12",
@"String 11",
@"Str?ng 21",// <-
@"String 02",nil];
NSLocale *currentLocale = [NSLocalecurrentLocale];
__blockNSUInteger orderedSameCount =0;
NSArray *diacriticInsensitiveSortArray = [stringsArraysortedArrayUsingComparator:^(id string1,id string2) {
NSRange string1Range =NSMakeRange(0, [string1length]);
NSComparisonResult comparisonResult = [string1compare:string2options:NSDiacriticInsensitiveSearchrange:string1Rangelocale:currentLocale];
if (comparisonResult ==NSOrderedSame) {
orderedSameCount++;
}
return comparisonResult;
}];
NSLog(@"diacriticInsensitiveSortArray: %@", diacriticInsensitiveSortArray);NSLog(@"orderedSameCount: %d", orderedSameCount);
/* Output:
diacriticInsensitiveSortArray: (
"String 02",
"string 1",
"String 11",
"string 12",
"String 21",
"Str\U00eeng 21",
"Stri\U00f1g 21"
)
orderedSameCount: 2
*/
這會在“Blocks和變量”部分裡面有更多的討論。
第二章 概念概述
Block對象提供了一個使用 C 語言和 C 派生語言(如Objective-C和C++)來創建表達式作為一個特別(ad hoc)的函數。在其他語言和環境中,一個block對象有時候被成為“閉包(closure)”。在這裡,它們通常被口語化為”塊(blocks)”,除非在某些范圍它們容易和標准 C 表達式的塊代碼混淆。
2.1 Block功能
一個 block就是一個匿名的內聯代碼集合體:
和函數一樣擁有參數類型
有推斷和聲明的返回類型
可以捕獲它的聲明所在相同作用域的狀態
可以和其他定義在相同作用域范圍的blocks進行共享更改
在相同作用域范圍(棧幀)被銷毀後持續共享和更改相同作用域范圍(棧幀)的狀
態
你可以拷貝一個 block,甚至可以把它作為可執行路徑傳遞給其他線程(或者在自己的線程內傳遞給run loop)。編譯器和運行時會在整個block生命周期裡面為所有block引用變量保留一個副本。盡管blocks在純 C 和 C++上面可用,但是一個block也同樣是一個Objective-C的對象。
2.2用處
Blocks通常代表一個很小、自包的代碼片段。因此它們作為封裝的工作單元在並發執行,或在一個集合項上,或當其他操作完成時的回調的時候非常實用。
Blocks作為傳統回調函數的一個實用的替代辦法,有以下兩個原因:
它們可以讓你在調用的地方編寫代碼實現後面將要執行的操作。
因此 Blocks通常作為框架方法的參數。
它們允許你訪問局部變量。
而不是需要使用一個你想要執行操作時集成所有上下文的信息的數據結構來
進行回調,你可以直接簡單的訪問局部變量。
第三章 聲明和創建Blocks3.1聲明一個block的引用
Block變量擁有blocks的引用。你可以使用和聲明函數指針類似的語法來聲明它們,除了它們使用^修飾符來替代*修飾符。Block類型可以完全操作其他 C 系統類型。以下都是合法的block聲明:
void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int,char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
Blocks還支持可變參數(...)。一個沒有使用任何參數的 block 必須在參數列表上面用void標明。
Blocks被設計為類型安全的,它通過給編譯器完整的元數據來合法使用blocks、傳遞到blocks的參數和分配的返回值。你可以把一個block引用強制轉換為任意類型的指針,反之亦然。但是你不能通過修飾符 * 來解引用一個block,因此一個block的大小是無法在編譯的時候計算的。
你同樣可以創建 blocks的類型。當你在多個地方使用同一個給定的簽名的block時,這通常被認為是最佳的辦法。
typedeffloat (^MyBlockType)(float,float);
MyBlockType myFirstBlock =// ... ;
MyBlockType mySecondBlock = // ... ;
3.2創建一個block
你可以使用 ^修飾符來標識一個block表達式的開始。它通常後面跟著一個被()包含起來的參數列表。Block的主體一般被包含在 {} 裡面。下面的示例定義了一個簡單的block,並把它賦值給前面聲明的變量(oneFrom)。這裡block使用一個標准 C的結束符;來結束。
int (^oneFrom)(int);
oneFrom = ^(int anInt) {
return anInt -1;
}
如果你沒有顯式的給 block 表達式聲明一個返回值,它會自動的從block內容推斷出來。如果返回值是推斷的,而且參數列表也是void,那麼你同樣可以省略參數列表的void。如果或者當出現多個返回狀態的時候,它們必須是完全匹配的(如果有必要可以使用強制轉換)。
3.3全局blocks
在文件級別,你可以把 block作為全局標示符:
#import
int GlobalInt =0;
int (^getGlobalInt)(void) = ^{return GlobalInt; };
第四章 Blocks和變量本文描述blocks和變量之間的交互,包括內存管理。
4.1變量類型
在 block的主體代碼裡面,變量可以被使用五種方法來處理。
你可以引用三種標准類型的變量,就像你在函數裡面引用那樣:
全局變量,包括靜態局部變量。
全局函數(在技術上而言這不是變量)。
封閉范圍內的局部變量和參數。
Blocks同樣支持其他兩種類型的變量:
在函數級別是__block變量。這些在block裡面是可變的(和封閉范圍),並任何引
用 block的都被保存一份副本到堆裡面。
引入const。
最後,在實現方法裡面,blocks也許會引用Objective-C的實例變量。參閱“對象
和 Block變量”部分。
在 block裡面使用變量遵循以下規則:
全局變量可訪問,包括在相同作用域范圍內的靜態變量。
傳遞給block的參數可訪問(和函數的參數一樣)。
程序裡面屬於同一作用域范圍的堆(非靜態的)變量作為const變量(即只讀)。
它們的值在程序裡面的 block 表達式內使用。在嵌套 block 裡面,該值在最近的
封閉范圍內被捕獲。
屬於同一作用域范圍內並被__block存儲修飾符標識的變量作為引用傳遞因此是
可變的。
屬於同一作用域范圍內block的變量,就和函數的局部變量操作一樣。
每次調用 block 都提供了變量的一個拷貝。這些變量可以作為 const來使用,或在block封閉范圍內作為引用變量。
下面的例子演示了使用本地非靜態變量:
int x =123;
void (^printXAndY)(int) = ^(int y) {
printf("%d %d\n", x, y);
};
printXAndY(456);// prints: 123 456
正如上面提到的,在 block 內試圖給 x 賦一個新值會導致錯誤發生:
int x =123;
void (^printXAndY)(int) = ^(int y) {
x = x + y; // error
printf("%d %d\n", x, y);
}
為了可以在 block內修改一個變量,你需要使用__block存儲類型修飾符來標識該變量。參閱“__block存儲類型”部分。
4.2 __block存儲類型
你可以指定引入一個變量為可更改的,即讀-寫的,通過應用__block 存儲類型修飾符。局部變量的__block的存儲和register、auto、static等存儲類型相似,但它們之間不兼容。
__block變量保存在變量共享的作用域范圍內,所有的blocks和block副本都聲明或創建在和變量的作用域相同范圍內。所以,如果任何blocks副本聲明在棧內並未超出棧的結束時,該存儲會讓棧幀免於被破壞(比如封裝為以後執行)。同一作用域范圍內給定的多個block可以同時使用一個共享變量。
作為一種優化,block存儲在棧上面,就像blocks本身一樣。如果使用Block_copy拷貝了block的一個副本(或者在Objective-C裡面給block發送了一條copy消息),變量會被拷貝到堆上面。所以一個__block變量的地址可以隨時間推移而被更改。
使用__block的變量有兩個限制:它們不能是可變長的數組,並且它們不能是包含有 C99 可變長度的數組變量的數據結構。
以下舉例說明了如何使用__block變量:
__blockint x =123;// x lives in block storage
void (^printXAndY)(int) = ^(int y) {
x = x + y;
printf("%d %d\n", x, y);
};
printXAndY(456);// prints: 579 456
// x is now 579
下面的例子顯示了 blocks 和其他幾個類型變量間的交互:
externNSInteger CounterGlobal;
staticNSInteger CounterStatic;
{
NSInteger localCounter =42;
__blockchar localCharacter;
void (^aBlock)(void) = ^(void) {
++CounterGlobal;
++CounterStatic;
CounterGlobal = localCounter;// localCounter fixed at block
creation
localCharacter ='a';// sets localCharacter in enclosing scope
};
++localCounter; // unseen by the block
localCharacter = 'b';
aBlock(); // execute the block
// localCharacter now 'a'
}
4.3對象(Object)和Block變量
Block 提供了支持Objective-C和Objective-C++的對象,和其他blocks的變量。
4.3.1Objective-C對象
在引用計數的環境裡面,默認情況下當你在block裡面引用一個Objective-C對象的時候,該對象會被retain。當你簡單的引用了一個對象的實例變量時,它同樣被retain。但是被__block存儲類型修飾符標記的對象變量不會被retain.注意:在垃圾回收機制裡面,如果你同時使用__weak 和__block 來標識一個變量,那麼該 block將不會保證它是一直是有效的。
如果你在實現方法的時候使用了 block,對象的內存管理規則更微妙:如果你通過引用來訪問一個實例變量,self會被retain。
如果你通過值來訪問一個實例變量,那麼變量會被retain。
下面舉例說明兩個方式的不同:
dispatch_async(queue, ^{
// instanceVariable is used by reference, self is retained
doSomethingWithObject(instanceVariable);
});
id localVariable = instanceVariable;
dispatch_async(queue, ^{
// localVariable is used by value, localVariable is retained (not self)
doSomethingWithObject(localVariable);
};
4.3.2C++對象
通常你可以在 block內使用C++的對象。在成員函數裡面,通過隱式的導入this指針引用成員變量和函數,結果會很微妙。有兩個條件可以讓block被拷貝:
如果你擁有__block存儲的類,它本來是一個基於棧的C++對象,那麼通常會使用copy的構造函數。
如果你在block裡面使用任何其他 C++基於棧的對象,它必須包含一個const copy的構造函數。該 C++對象使用該構造函數來拷貝。
4.3.3Blocks
當你拷貝一個 block時,任何在該block裡面對其他blocks的引用都會在需要的時候被拷貝,即拷貝整個目錄樹(從頂部開始)。如果你有block變量並在該block裡面引用其他的block,那麼那個其他的block會被拷貝一份。
當你拷貝一個基於棧的 block 時,你會獲得一個新的block。但是如果你拷貝一個基於堆的block,你只是簡單的遞增了該block的引用數,並把原始的block作為函數或方法的返回值。
第五章 使用Blocks
5.1調用一個Block
如果你聲明了一個 block作為變量,你可以把它作為一個函數來使用,如下面的
兩個例子所示:
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
然而你通常會把 block作為參數傳遞給一個函數或方法。在這種情況下,你通過需要創建一個”內聯(inline)”的block。
5.2使用Block作為函數的參數
你可以把一個 block作為函數的參數就像其他任何參數那樣。然而在很多情況下,你不需要聲明blocks;相反你只要簡單在需要它們作為參數的地方內聯實現它們。下面的例子使用qsort_b函數。qsort_b和標准qsort_r函數類似,但是它最後一個參數用block.
char *myCharacters[3] = {"TomJohn","George","Charles Condomine" };
qsort_b(myCharacters,3,sizeof(char *), ^(constvoid *l,constvoid *r) {
char *left = *(char **)l;
char *right = *(char **)r;
returnstrncmp(left, right,1);
});
// Block implementation ends at "}"
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
注意函數參數列表包含了一個 block。
下一個例子顯示了如何在 dispatch_apply 函數裡面使用block。dispatch_apply聲明如下:
void dispatch_apply(size_t iterations,dispatch_queue_t queue,void (^block)(size_t));
該函數提交一個 block給批處理隊列來多次調用。它需要三個參數;第一個指定迭代器的數量;第二個指定一個要提交block的隊列;第三個是block它本身,它自己需要一個參數(當前迭代器的下標)。
你可以使用 dispatch_apply來簡單的打印出迭代器的下標,如下:
#include
size_t count =10;
dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_apply(count, queue, ^(size_t i) {
printf("%u\n", i);
});
5.3使用Block作為方法的參數
Cocoa提供了一系列使用block的方法。你可以把一個block作為方法的參數就像其他參數那樣。
下面的例子確定數組前面五個元素中第一個出現在給定的過濾器集中任何一個
的下標。
NSArray *array = [NSArrayarrayWithObjects:@"A",@"B",@"C",@"A",@"B",@"Z",@"G",@"are",@"Q",nil];
NSSet *filterSet = [NSSetsetWithObjects:@"A",@"Z",@"Q",nil];
BOOL (^test)(id obj,NSUInteger idx,BOOL *stop);
test = ^ (id obj,NSUInteger idx,BOOL *stop) {
if (idx <5) {
if ([filterSetcontainsObject: obj]) {
returnYES;
}
}
returnNO;
};
NSIndexSet *indexes = [arrayindexesOfObjectsPassingTest:test];
NSLog(@"indexes: %@", indexes);
/*
Output:
indexes: [number of indexes: 2 (in 2 ranges), indexes: (0 3)]
*/
下面的例子確定一個 NSSet 是否包含一個由局部變量指定的單詞,並且如果條件成立把另外一個局部變量(found)設置為 YES(並停止搜索)。注意到found同時被聲明為__block變量,並且該block是內聯定義的:
__blockBOOL found =NO;
NSSet *aSet = [NSSetsetWithObjects:@"Alpha",@"Beta",@"Gamma",@"X",nil];
NSString *string =@"gamma";
[aSet enumerateObjectsUsingBlock:^(id obj,BOOL *stop) {
if ([objlocalizedCaseInsensitiveCompare:string] ==NSOrderedSame) {
*stop = YES;
found = YES;
}
}];
// At this point, found == YES
5.4拷貝Blocks
通常,你不需要 copy(或retain)一個block.在你希望block在它被聲明的作用域被銷毀後繼續使用的話,你子需要做一份拷貝。拷貝會把 block 移到堆裡面。
你可以使用 C 函數來 copy 和release一個block:
Block_copy();
Block_release();
如果你使用 Objective-C,你可以給一個block發送copy、retain和release(或autorelease)消息。
為了避免內存洩露,你必須總是平衡Block_copy()和Block_release()。你必須平衡copy或retain和release(或autorelease)--除非是在垃圾回收的環境裡面。
5.5需要避免的模式
一個 block的文本(通常是^{...})是一個代表block的本地棧數據結構地址。因此該本地棧數據結構的作用范圍是封閉的復合狀態,所以你應該避免下面例子顯示的模式:
void dontDoThis() {
void (^blockArray[3])(void);// an array of 3 block references
for (int i =0; i <3; ++i) {
blockArray[i] = ^{ printf("hello, %d\n", i); };
// WRONG: The block literal scope is the "for" loop
}
}
void dontDoThisEither() {
void (^block)(void);
int i =random():
if (i >1000) {
block = ^{ printf("got i at: %d\n", i); };
// WRONG: The block literal scope is the "then" clause
}
// ...
}
5.6調試
你可以在 blocks裡面設置斷點並單步調試。你可以在一個 GDB 的對話裡面使用
invoke-block來調用一個block。如下面的例子所示:
$ invoke-block myBlock 1020
如果你想傳遞一個 C 字符串,你必須用引號括這它。例如,為了傳遞this string給doSomethingWithString的block,你可以類似下面這樣寫:
$ invoke-block doSomethingWithString "\"this string\""
結束語
Block是iOS 4.0之後添加的新特性支持。本人親測感覺使用Block最大的便利就是簡化的回調過程。以前使用UIView的動畫,進程要控制動畫結束後進行相應的處理。iOS 4.0之後,UIView新增了對Block的支持,現在只要使用簡單的一個Block代碼就可以在寫動畫的代碼部分直接添加動畫結束後的操作。還有就是在使用
Notification時候Block也非常有幫助。反正多用就可以體會到Block的優美了。
對了,使用 Block要謹記別造成對象互相引用對方導致引用計數進入一個循環導致對象無法被釋放。iOS 5.0之後的 ARC也是無法解決該潛在的互相引用的問題的。所以寫Block的時候要注意這點。因為Block往往在後台自動對一些它引用了的對象進行retain操作。具體形式這裡就不距離了,大家在使用的時候多體會一下。