from http://www.cnbluebox.com/?p=255
php
引言 html
使用block已經有一段時間了,感受本身瞭解的還行,可是幾天前看到CocoaChina上一個關於block的小測試主題 : 【小測試】你真的知道blocks在Objective-C中是怎麼工做的嗎?,發現居然作錯了幾道, 才知道本身想固然的理解是錯誤的,因此抽時間學習了下,而且經過一些測試代碼進行測試,產生這篇博客。 閉包
Block簡介(copy一段) app
Block做爲C語言的擴展,並非高新技術,和其餘語言的閉包或lambda表達式是一回事。須要注意的是因爲Objective-C在iOS中不支持GC機制,使用Block必須本身管理內存,而內存管理正是使用Block坑最多的地方,錯誤的內存管理 要麼致使return cycle內存泄漏要麼內存被提早釋放致使crash。 Block的使用很像函數指針,不過與函數最大的不一樣是:Block能夠訪問函數之外、詞法做用域之內的外部變量的值。換句話說,Block不僅 實現函數的功能,還能攜帶函數的執行環境。 dom
能夠這樣理解,Block其實包含兩個部份內容 async
Block與函數另外一個不一樣是,Block相似ObjC的對象,可使用自動釋放池管理內存(但Block並不徹底等同於ObjC對象,後面將詳細說明)。 函數
Block基本語法 學習
基本語法在本文就不贅述了,同窗們自學。 測試
Block的類型與內存管理 spa
根據Block在內存中的位置分爲三種類型NSGlobalBlock,NSStackBlock, NSMallocBlock。
1、NSGlobalBlock以下,我們能夠經過是否引用外部變量識別,未引用外部變量即爲NSGlobalBlock,能夠當作函數使用。
1 2 3 4 5 6 7 8 9 |
{ //create a NSGlobalBlock float (^sum)(float, float) = ^(float a, float b){
return a + b; };
NSLog(@"block is %@", sum); //block is <__NSGlobalBlock__: 0x47d0> } |
2、NSStackBlock以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ NSArray *testArr = @[@"1", @"2"];
void (^TestBlock)(void) = ^{
NSLog(@"testArr :%@", testArr); };
NSLog(@"block is %@", ^{
NSLog(@"test Arr :%@", testArr); }); //block is <__NSStackBlock__: 0xbfffdac0> //打印可看出block是一個 NSStackBlock, 即在棧上, 當函數返回時block將無效
NSLog(@"block is %@", TestBlock); //block is <__NSMallocBlock__: 0x75425a0> //上面這句在非arc中打印是 NSStackBlock, 可是在arc中就是NSMallocBlock //即在arc中默認會將block從棧複製到堆上,而在非arc中,則須要手動copy. } |
3、NSMallocBlock只須要對NSStackBlock進行copy操做就能夠獲取,可是retain操做就不行,會在下面說明
Block的copy、retain、release操做 (仍是copy一段)
不一樣於NSObjec的copy、retain、release操做:
Block對外部變量的存取管理
基本數據類型
1、局部變量
局部自動變量,在Block中只讀。Block定義時copy變量的值,在Block中做爲常量使用,因此即便變量的值在Block外改變,也不影響他在Block中的值。
1 2 3 4 5 6 7 8 9 10 11 |
{ int base = 100; long (^sum)(int, int) = ^ long (int a, int b) {
return base + a + b; };
base = 0; printf("%ld\n",sum(1,2)); // 這裏輸出是103,而不是3, 因爲塊內base爲拷貝的常量 100 } |
2、STATIC修飾符的全局變量
因爲全局變量或靜態變量在內存中的地址是固定的,Block在讀取該變量值的時候是直接從其所在內存讀出,獲取到的是最新值,而不是在定義時copy的常量.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ static int base = 100; long (^sum)(int, int) = ^ long (int a, int b) { base++; return base + a + b; };
base = 0; printf("%ld\n",sum(1,2)); // 這裏輸出是4,而不是103, 因爲base被設置爲了0 printf("%d\n", base); // 這裏輸出1, 因爲sum中將base++了 } |
3、__BLOCK修飾的變量
Block變量,被__block修飾的變量稱做Block變量。 基本類型的Block變量等效於全局變量、或靜態變量。
注:BLOCK被另外一個BLOCK使用時,另外一個BLOCK被COPY到堆上時,被使用的BLOCK也會被COPY。但做爲參數的BLOCK是不會發生COPY的
OBJC對象
block對於objc對象的內存管理較爲複雜,這裏要分static global local block變量分析、還要分非arc和arc分析
非ARC中的變量
先看一段代碼(非arc)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
@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, char *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。
ARC中的變量測試
因爲arc中沒有retain,retainCount的概念。只有強引用和弱引用的概念。當一個變量沒有__strong的指針指向它時,就會被系統釋放。所以咱們能夠經過下面的代碼來測試。
代碼片斷1(globalObject全局變量)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
NSString *__globalString = nil;
- (void)testGlobalObj { __globalString = @"1"; void (^TestBlock)(void) = ^{
NSLog(@"string is :%@", __globalString); //string is http://www.cnbluebox.com/blog/wp-includes/images/smilies/icon_sad.gif" alt=":(" class="wp-smiley"> null) };
__globalString = nil;
TestBlock(); }
- (void)testStaticObj { static NSString *__staticString = nil; __staticString = @"1";
printf("static address: %p\n", &__staticString); //static address: 0x6a8c
void (^TestBlock)(void) = ^{
printf("static address: %p\n", &__staticString); //static address: 0x6a8c
NSLog(@"string is : %@", __staticString); //string is http://www.cnbluebox.com/blog/wp-includes/images/smilies/icon_sad.gif" alt=":(" class="wp-smiley"> null) };
__staticString = nil;
TestBlock(); }
- (void)testLocalObj { NSString *__localString = nil; __localString = @"1";
printf("local address: %p\n", &__localString); //local address: 0xbfffd9c0
void (^TestBlock)(void) = ^{
printf("local address: %p\n", &__localString); //local address: 0x71723e4
NSLog(@"string is : %@", __localString); //string is : 1 };
__localString = nil;
TestBlock(); }
- (void)testBlockObj { __block NSString *_blockString = @"1";
void (^TestBlock)(void) = ^{
NSLog(@"string is : %@", _blockString); // string is http://www.cnbluebox.com/blog/wp-includes/images/smilies/icon_sad.gif" alt=":(" class="wp-smiley"> null) };
_blockString = nil;
TestBlock(); }
- (void)testWeakObj { NSString *__localString = @"1";
__weak NSString *weakString = __localString;
printf("weak address: %p\n", &weakString); //weak address: 0xbfffd9c4 printf("weak str address: %p\n", weakString); //weak str address: 0x684c
void (^TestBlock)(void) = ^{
printf("weak address: %p\n", &weakString); //weak address: 0x7144324 printf("weak str address: %p\n", weakString); //weak str address: 0x684c
NSLog(@"string is : %@", weakString); //string is :1 };
__localString = nil;
TestBlock(); } |
由以上幾個測試咱們能夠得出:
1、只有在使用local變量時,block會複製指針,且強引用指針指向的對象一次。其它如全局變量、static變量、block變量等,block不會拷貝指針,只會強引用指針指向的對象一次。
2、即時標記了爲__weak或__unsafe_unretained的local變量。block仍會強引用指針對象一次。(這個不太明白,由於這種寫法可在後面避免循環引用的問題)
循環引用retain cycle
循環引用指兩個對象相互強引用了對方,即retain了對方,從而致使誰也釋放不了誰的內存泄露問題。如聲明一個delegate時通常用assign而不能用retain或strong,因爲你一旦那麼作了,很大可能引發循環引用。在以往的項目中,我幾回用動態內存檢查發現了循環引用致使的內存泄露。
這裏講的是block的循環引用問題,由於block在拷貝到堆上的時候,會retain其引用的外部變量,那麼若是block中若是引用了他的宿主對象,那頗有可能引發循環引用,如:
1 2 3 4 |
self.myblock = ^{
[self doSomething]; }; |
爲測試循環引用,寫了些測試代碼用於避免循環引用的方法,以下,(只有arc的,懶得作非arc測試了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
- (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; } } |
通過上面的測試發現,在加了__weak和__unsafe_unretained的變量引入後,TestCycleRetain方法能夠正常執行dealloc方法,而不轉換和用__block轉換的變量都會引發循環引用。
所以防止循環引用的方法以下:
__unsafe_unretained TestCycleRetain *weakSelf = self;
end
補充:
In manual reference counting mode, __block id x; has the effect of not retaining x. In ARC mode, __block id x; defaults to retaining x (just like all other values). To get the manual reference counting mode behavior under ARC, you could use __unsafe_unretained __block id x;. As the name __unsafe_unretained implies, however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use __weak (if you don’t need to support iOS 4 or OS X v10.6), or set the __block value to nilto break the retain cycle.
本文來自臺灣的某開發人員的博客,被牆,感受講的比較易懂,因此引過來。文字簡體化了,原來是繁體,變數=變量,這個注意一下。
本文的順序是層層深刻的,要想簡單的瞭解,只看X.1 初探Block就行了
本章學習目標:
1. 瞭解何謂block。
2. 瞭解block的使用方法。
Block 是iOS在4.0以後新增的程式語法,嚴格來講block的概念並不算是基礎程式設計的範圍,對初學者來講也不是很容易瞭解,可是在iOS SDK 4.0以後,block幾乎出如今全部新版的API之中,換句話說,若是不瞭解block這個概念就沒法使用SDK 4.0版本之後的新功能,所以雖然block自己的語法有點難度,但爲了使用iOS的新功能咱們仍是得硬着頭皮去了解這個新的程式概念。
在這一章的目標以瞭解如何使用block爲主而不深刻探討block底層的運做方式,至於有些初學者較少遇到的辭彙如「詞法做用域(lexical scope)」等,本章將再也不多作解釋,待有興趣的讀者去請教Google大神吧。
X.1 初探Block
在這一小節咱們先用一些簡單範例來導入block的概念。
X.1.1 宣告和使用Block
咱們使用「^」運算子來宣告一個block變數,並且在block的定義最後面要加上「;」來表示一個完整的述句(也就是將整個block定義視爲前面章節所介紹的簡單述句,由於整個定義必須是一個完整的句子,因此必須在最後面加上分號),下面是一個block的範例:
1: int multiplier = 7 ;
2: int (^myBlock)( int ) = ^( int num)
3: {
4: return num * multiplier;
5: };
咱們使用下圖來解釋這個範例(請將文字框的字翻譯以下):
咱們宣告一個「myBlock」變數,用「^」符號來表示這是一個block。
這是block的完整定義,這個定義將會指定給「myBlock」變數。
表示「myBlock」是一個回傳值爲整數(int)的block。
它有一個參數,型態也是整數。
這個參數的名字叫作「num」。
這是block的內容。
值得注意的地方是block可使用和自己定義範圍相同的變數,能夠想像在上面的例子中 multiplier 和 myBlock 都是某一個函數內定義的兩個變數也就是這個變數都在某個函數兩個大括號「{」和「 }」中間的區塊,由於它們的有效範圍是相同的,所以在block中就能夠直接使用 multiplier 這個變數,此外當把block定義成一個變數的時,咱們能夠直接像使用通常函數般的方式使用它:
1: int multiplier = 7 ;
2: int (^myBlock)( int ) = ^( int num)
3: {
4: return num * multiplier;
5: };
6: printf ( "%d" , myBlock( 3 ));
7: //結果會打印出21
X.1.2 直接使用Block
在不少狀況下,咱們並不須要將block宣告成變數,反之咱們能夠直接在須要使用block的地方直接用內嵌的方式將block的內容寫出來,在下面的例子中qsort_b函數,這是一個相似傳統的qsort_t函數,可是直接使用block作爲它的參數:
1: char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };
2: qsort_b (myCharacters, 3 ,
3: sizeof ( char *),
4: ^( const void *l, const void *r)//block部分
5: {
6: char *left = *( char **)l;
7: char *right = *( char **)r;
8: return strncmp (left, right, 1 );
9: } //end
10: );
X.1.3 __block 變量
通常來講,在block內只能讀取在同一個做用域的變數並且沒有辦法修改在block外定義的任何變數,此時若咱們想要這些變數可以在block中 被修改,就必須在前面掛上__block的修飾詞,以上面第一個例子中的 multiplier 來講,這個變數在 block 中是惟讀的,因此 multiplier = 7 指定完後,在 block 中的 multiplier 就只能是 7 不能修改,若咱們在 block 中修改 multiplier ,在編輯時就會產生錯誤,所以若想要在 block 中修改 multiplier ,就必須在 multiplier 前面加上 __block 的修飾詞,請參考下面的範例:
1: __block int multiplier = 7 ;
2: int (^myBlock)( int ) = ^( int num)
3: {
4: if (num > 5 )
5: {
6: multiplier = 7 ;
7: }
8: else
9: {
10: multiplier = 10 ;
11: }
12: return num * multiplier;
13: };
Block 提供咱們一種可以將函數程式碼內嵌在通常述句中的方法,在其餘語言中也有相似的概念稱作「closure」,可是爲了配合Objective-C的貫例,咱們一概將這種用法稱爲「block」
X.2.1 Block 的功能
Block 是一種具備匿名功能的內嵌函數,它的特性以下:
如通常的函數般能擁有帶有型態的參數。
擁有回傳值。
能夠擷取被定義的詞法做用域(lexical scope)狀態。
能夠選擇性地修改詞法做用域的狀態。
注:詞法做用域(lexical scope)能夠想像成是某個函數兩個大括號中間的區塊,這個區塊在程式執行時,系統會將這個區塊放入堆疊記憶體中,在這個區塊中的宣告的變數就像是咱們 常聽到的區域變數,當咱們說block能夠擷取同一詞法做用域的狀態時能夠想像block變數和其餘區域變數是同一個層級的區域變數(位於同一層的堆疊 裏),而block的內容能夠讀取到和他同一層級的其餘區域變數。
咱們能夠拷貝一個block,也能夠將它丟到其餘的執行緒中使用,基本上雖然block在iOS程式開發中可使用在C/C++開發的程式片斷,也能夠在Objective-C中使用,不過在系統的定義上,block永遠會被視爲是一個Objective-C的物件。
X.2.2 Block 的使用時機
Block 通常是用來表示、簡化一小段的程式碼,它特別適合用來創建一些同步執行的程式片斷、封裝一些小型的工做或是用來作爲某一個工做完成時的回傳呼叫(callback) 。
在新的iOS API中block被大量用來取代傳統的delegate和callback,而新的API會大量使用block主要是基於如下兩個緣由:
能夠直接在程式碼中撰寫等會要接着執行的程式,直接將程式碼變成函數的參數傳入函數中,這是新API最常使用block的地方。
能夠存取區域變數,在傳統的callback實做時,若想要存取區域變數得將變數封裝成結構才能使用,而block則是能夠很方便地直接存取區域變數。
X.3.1 宣告Block的參考(Reference)
Block 變數儲存的是一個block的參考,咱們使用相似宣告指標的方式來宣告,不一樣的是這時block變數指到的地方是一個函數,而指標使用的是「*」,block則是使用「^」來宣告,下面是一些合法的block宣告:
1: /* 回傳void ,參數也是void 的block*/
2: void (^blockReturningVoidWithVoidArgument)( void );
3: /* 回傳整數,兩個參數分別是整數和字元型態的block*/
4: int (^blockReturningIntWithIntAndCharArguments)( int , char );
5: /* 回傳void ,含有10 個block 的陣列,每一個block 都有一個型態爲整數的參數*/
6: void (^arrayOfTenBlocksReturningVoidWinIntArgument[ 10 ])( int );
7: X.3.2 創建一個Block
8:
9: 咱們使用「^」來開始一個block,並在最後使用「;」來表示結束,下面的範例示範了一個block變數,而後再定義一個block把它指定給block變數:
10:
11: int (^oneFrom)( int ); /* 宣告block 變數*/
12: /* 定義block 的內容並指定給上面宣告的變數*/
13: oneFrom = ^(int anInt)
14: {
15: return anInt = - 1 ;
16: };
X.3.3 全域的Block
我在能夠在檔案中宣告一個全域的block,請參考如下範例:
1: int GlobalInt = 0 ;
2: int (^getGlobalInt)( void ) = ^ ( void ) { return GlobalInt ;};
接下來的這一小節咱們將會介紹block和變數之間的互動。
X.4.1 變數的型態
咱們能夠在block中遇到日常在函數中會遇到的變數類型:
l 全域(global)變數或是靜態的區域變數(static local)。
l 全域的函數。
l 區域變數和由封閉領域(enclosing scope)傳入的參數。
除了上述以外block額外支援了另外兩種變數:
在函數內可使用__block 變數,這些變數在block中是可被修改的。
匯入常數(const imports)。
此外,在方法的實做裏,block可使用Objective-C的實體變數(instance variable)。
下列的規則能夠套用到在block中變數的使用:
能夠存取全域變數和在同一領域(enclosing lexical scope)中的靜態變數。
能夠存取傳入block的參數(使用方式和傳入函數的參數相同)。
在同一領域的區域變數在block中將視爲常數(const)。
能夠存取在同一領域中以__block 爲修飾詞的變數。
在block中宣告的區域變數,使用方式和日常函數使用區域變數的方式相同。
下面的例子介紹了區域變數(上述第三點)的使用方式:
1: int x = 123 ;
2: void (^printXAndY)( int ) = ^( int y)
3: {
4: printf ( "%d %d\n" , x, y);
5: };
6: // 將會印出123 456
7: printXAndY( 456 );
8: 就如上面第三點所提到的,在上例中的int x = 123的變量x,在傳入block後將視同常數,所以若咱們在block中試着去修改x的值時就會產生錯誤,下面的例子將會沒法經過編譯:
9:
10: int x = 123 ;
11: void (^printXAndY)( int ) = ^( int y)
12: {
13: // 下面這一行是錯的,由於x 在這是一個常數不能被修改。
14: x = x + y;
15: printf ( "%d %d\n" , x, y);
16: };
若在block中想要修改上面的變數x,必須將x宣告加上修飾詞__block,請參考接下來這一小節的介紹。
X.4.2 __block 型態變數
咱們能夠藉由將一個由外部匯入block的變數放上修飾詞__block來讓這個變數由惟讀變成能夠讀和寫,不過有一個限制就是傳入的變數在記憶體 中必須是一個佔有固定長度記憶體的變數,__block修飾詞沒法使用於像是變更長度的陣列這類不定長度的變數,請參考下面的範例:
1: // 加上__block 修飾詞,因此能夠在block 中被修改。
2: __block int x = 123 ;
3: void (^printXAndY)( int ) = ^( int y)
4: {
5: x = x + y;
6: printf ( "%d %d\n" , x, y);
7: };
8: // 將會印出579 456
9: printXAndY( 456 );
10: //x 將會變成 579;
11: 下面咱們使用一個範例來介紹各種型的變數和block之間的互動:
12:
13: extern NSInteger CounterGlobal;
14: static NSInteger CounterStatic;
15: {
16: NSInteger localCounter = 42 ;
17: __block char localCharacter;
18: void (^aBlock)( void ) = ^( void )
19: {
20: ++ CounterGlobal ; //能夠存取。
21: ++ CounterStatic ; //能夠存取。
22: CounterGlobal = localCounter; //localCounter在block 創建時就不可變了。
23: localCharacter = 'a' ; //設定外面定義的localCharacter 變數。
24: };
25: ++localCounter; //不會影響的block 中的值。
26: localCharacter = 'b' ;
27: aBlock(); //執行block 的內容。
28: //執行完後,localCharachter 會變成'a'
29: }
X.4.3 物件和Block變數
Block 支援在Objective-C、C++物件和其餘block中看成變數來使用,不過由於在大部分的狀況咱們都是使用Objective-C的撰寫程式,因 此在這一小節咱們僅針對Objective-C的狀況進行介紹,至於其餘兩種狀況就留給有興趣的讀者再自行深刻研究了。
x.4.3.1 Objective-C 物件
在擁有參考計數(reference-counted)的環境中,若咱們在block中參考到Objective-C的物件,在通常的狀況下它將會自動增長物件的參考計數,不過若以__block爲修飾詞的物件,參考計數則是不受影響。
若是咱們在Objective-C的方法中使用block時,如下幾個和記憶體管理的事是須要額外注意的:
l 若直接存取實體變數(instance variable),self的參考計數將被加1。
l 若透過變數存取實體變數的值,則只變數的參考計數將被加1。
如下程式碼說明上面兩種狀況,在這個假設instanceVariable是實體變數:
1: dispatch_async (queue, ^{
2: // 由於直接存取實體變數instanceVariable ,因此self 的retain count 會加1
3: doSomethingWithObject (instanceVariable);
4: });
5: id localVaribale = instanceVariable;
6: dispatch_async (queue, ^{
7: //localVariable 是存取值,因此這時只有localVariable 的retain count 加1
8: //self 的 return count 並不會增長。
9: doSomethingWithObject (localVaribale);
10: });
這一小節咱們將會對block的使用方式作一些初步的介紹
X.5.1 呼叫一個Block
當block宣告成一個變數時,咱們能夠像使用通常函數的方式來使用它,請參考下面兩個範例:
1: int (^oneFrom)( int ) = ^( int anInt) {
2: return anInt - 1 ;
3: };
4: printf ( "1 from 10 is %d" , oneFrom( 10 ));
5: //結果會顯示:1 from 10 is 9
6: float (^distanceTraveled)( float , float , float ) = ^( float startingSpeed, float acceleration, float time)
7: {
8: float distance = (startingSpeed * time) + ( 0.5 * acceleration * time * time);
9: return distance;
10: };
11: float howFar = distanceTraveled( 0.0 , 9.8 , 1.0 );
12: //howFar會變成4.9
在通常常見的狀況中,如果將block當作是參數傳入函數,咱們一般會使用「內嵌」的方式來使用block。
X.5.2 將Block看成函數的參數
咱們能夠像使用通常函數使用參數的方式,將block以函數參數的型式傳入函數中,在這種狀況下,大多數咱們使用block的方式將不會傾向宣告block而是直接之內嵌的方式來將block傳入,這也是目前新版SDK中主流的作法,咱們將補充前面章節的例子來講明:
1: char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };
2: qsort_b (myCharacters, 3 , sizeof ( char *),
3: ^( const void *l, const void *r)
4: {
5: char *left = *( char **)l;
6: char *right = *( char **)r;
7: return strncmp (left, right, 1 );
8: } // 這裏是block 的終點。
9: );
10: // 最後的結果爲:{"Charles Condomine", "George", "TomJohn"}
在上面的例子中,block自己就是函數參數的一部分,在下一個例子中dispatch_apply函數中使用block,dispatch_apply的定義以下:
1: void
2: dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)( size_t ));
3: 這個函數將一個block提交到發送佇列(dispatch queue)中來執行多重的呼叫,只有當佇列中的工做都執行完成後纔會回傳,這個函數擁有三個變數,而最後一個參數就是block ,請參考下面的範例:
4:
5: size_t count = 10 ;
6: dispatch_queue_t queue =
7: dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 );
8: dispatch_apply (count, queue, ^( size_t i) {
9: printf ( "%u\n" , i);
10: });
X.5.3 將Block看成方法的參數
在SDK中提供了許多使用block的方法,咱們能夠像傳遞通常參數的方式來傳遞block,下面這個範例示範如何在一個陣列的前5筆資料中取出咱們想要的資料的索引值:
1: // 全部的資料
2: NSArray *array = [ NSArray arrayWithObjects : @"A" , @"B" , @"C" , @"A" , @"B" , @"Z" , @"G" , @"are" , @" Q" ,nil ];
3: // 咱們只要這個集合內的資料
4: NSSet *filterSet = [ NSSet setWithObjects : @"A" , @"B" , @"Z" , @"Q" , nil ];
5: BOOL (^test)( id obj, NSUInteger idx, BOOL *stop);
6: test = ^ ( id obj, NSUInteger idx, BOOL *stop) {
7: // 只對前5 筆資料作檢查
8: if (idx < 5 ) {
9: if ([filterSet containsObject : obj]) {
10: return YES ;
11: }
12: }
13: return NO ;
14: };
15: NSIndexSet *indexes = [array indexesOfObjectsPassingTest :test];
16: NSLog ( @"indexes: %@" , indexes);
17: // 結果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)]
18: // 前5筆資料中,有4筆符合條件,它們的索引值分別是0-1, 3-4
X.5.4 該避免的使用方式在下面的例子中,block是for迴圈的區域變數所以在使用上必須避免將區域的block指定給外面宣告的block:
1: // 這是錯誤的範例,請勿在程式中使用這些語法!!
2: void dontDoThis() {
3: void (^blockArray[3])(void); // 3 個block 的陣列
4: for (int i = 0; i < 3; ++i) {
5: blockArray[i] = ^{ printf("hello, %d\n", i); };
6: // 注意: 這個block 定義僅在for 迴圈有效。
7: }
8: }
9: void dontDoThisEither() {
10: void (^block)(void);
11: int i = random():
12: if (i > 1000) {
13: block = ^{ printf("got i at: %d\n", i); };
14: // 注意: 這個block 定義僅在if 後的兩個大括號中有效。
15: }
16: // ...
17: }