本文是對Apple的《Blocks Progromming Gude》學習的筆記總結。
對象時C級別的語法和運行時特性。和標准C函數很類似,但除了可執行代碼外,還可能包含了變量自動綁定(棧)或內存托管(堆)。所以一個block維護一個狀態集(數據),可以在執行的時候用來影響程序行為。Block用來作為回調特別有用。
你可以在MAC OS 10.6及其以后版本、IOS 4.0及其以后版本上使用Blocks.
Blocks運行時是開源的,可以再LLVM's compiler-rt subproject repository(LLVM的RT編譯器的子項目)里面找到他。
1.聲明一個Block
1 int (^myBlock) (int) = ^(int num){ 2 return num * num; 3 } 4 說明: 5 int:返回值類型,如果沒有返回值則為void 6 (^myBlock):塊定義需要有一個^標記,myBlock是塊名稱 7 (int):參數類型列表,如果沒有參數則為void 8 ^(int num):以^開頭的參數列表,如果沒有則為void,也可以省略 9 {}:block體(相當於函數體)
2.使用block
1 printf("%d",myBlock(7));
3.很多情況下不需要對block進行生命,而是直接使用內聯的block
1 char *myCharacters[3] = {"TomJohn","George","Jim Green"}; 2 3 qsort_b(myCharacters,3,sizeof(char *),^(const void *l,const void *r){ 4 char *left = *(char **)l; 5 char *right = *(char **)r; 6 return strncmp(left,right,l); 7 });
4.在Cocoa frameworks中有一些方法使用block作為參數,通常不是執行對一個對象的集合的操作就是在操作完成的時候作為回調使用。例子中顯示了如何通過NSArray的sortedArrayUsingComparator:使用block.
1 -(void) sort{ 2 NSArray *stringArray = [NSArray arrayWithObjects:@"Java網絡編程",@"Java開發實戰經典",@"研磨設計模式",@"iPhone開發秘籍",@"自己動手寫網絡爬蟲",@"Cocos2d 游戲開發實戰",@"Oracle寶典",@"Java Web開發實戰",nil]; 3 static NSStringCompareOptions options = NSCaseInsensitiveSearch | 4 NSNumericSearch | 5 NSWidthInsensitiveSearch | 6 NSForcedOrderingSearch; 7 NSLocale *currentLocale = [NSLocale currentLocale]; 8 NSComparator sortBlock = ^(id string1,id string2){ 9 NSRange string1Range = NSMakeRange(0, [string1 length]); 10 return [string1 compare:string2 options:options range:string1Range locale:currentLocale]; 11 }; 12 NSArray *sortedArray = [stringArray sortedArrayUsingComparator:sortBlock]; 13 NSLog(@"array:%@",sortedArray); 14 }
5、Blocks默認不能修改相同作用域范圍內的變量,但是如果這個相同作用域的變量如果使用了__block關鍵字進行修飾,則可以通過blocks進行修改。
6、一個block就是一個匿名的內聯代碼集合體:
-
*和函數一樣擁有參數類型
-
*有推斷和聲明的返回值類型
-
*可以捕獲它的聲明所在的相同作用域的狀態,可以和其它定義在相同作用於范圍的bolcks進行共享更改
-
*在相同作用域范圍被銷毀后持續共享和更改相同作用域范圍的狀態
7、Blocks通常代表一個很小、自包的代碼段。因此他們作為封裝的工作單元在並發執行或者一個集合項上或者當其他操作執行完畢時的回調的時候非常使用。
Blocks作為傳統的回調函數的一個實用的替代方法有以下兩個原因:
它們可以讓你在調用的地方編寫代碼實現后面將要執行的操作。因此Blocks通常作為Frameworks 方法的參數
它們允許你訪問局部變量,而不是需要使用一個你想要執行操作時繼承所有上下文信息的數據結構來進行回調。你可以直接簡單的訪問局部變量。
8、blocks和一個函數指針很相似,除了使用^來代替*。下面的都是合法的block的聲明:
1 void (^blockReturningVoidWithVoidArgument)(void); 2 int (^blockReturningIntWIthIntndCharArguments)(int,char); 3 void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int); 4 5 Blocks還支持可變參數(...).一個沒有使用任何參數的block必須在參數列表上用void標明。
9、可以把一個bolcks強制轉換成任意類型,反之亦然,但是不能通過*修飾符來解引用一個block,因此一個block的帶笑傲是無法在編譯的時候計算的。你同樣可以創建block的類型,擋在多個地方使用同一個給定簽名的block的時候,這通常被認為是最佳的方法:
1 typedef float (^MyBlockType) (float,float); 2 MyBlockType myFirstBlock = ...; 3 MyBlockType mySecondBlock = ...;
10、創建一個Block
1 int (^oneFrom) (int); 2 oneFrom = ^(int anInt){ 3 return anInt - 1; 4 }; 5 如果咩有顯式的給一個block表達式聲明一個返回值,它會自動的從block內容推斷出來。如果返回值是推斷的,而且參數列表也是void,那么你同樣可以省略參數列表的void。如果或者當出現多個返回狀態的時候,它們必須是完全匹配的(如果有必要可以使用強制類型轉換)
11、全局Blocks
在文件級別,可以把block作為全局標示符:
1 #import <stdio.h> 2 3 int GlobalInt = 0; 4 int (^getGaobalInt)(void) = ^{return GlobalInt;};
12、在block的主體代碼中,變量可以被使用五種方法來處理。可以引用三種標准類型的變量,就像在函數里面引用一樣:
全局變量:包括靜態局部變量
全局函數:在技術上說這不是變量
封閉范圍內的局部變量和參數
*在函數界別是__block變量。這些在block里面是可變的,並且任何引用block的都被保存一份副本到堆里面。
*引用const
*最后在實現方法里面,blocks也許會引用Objective-C的實例變量。
在Blocks里面使用變量遵循以下的規則:
全局變量可以訪問,包括在相同作用域范圍內的靜態變量
傳遞給block的參數可以訪問(和函數的參數一樣)
程序里面屬於同一作用域范圍的堆變量作為const變量(只讀)。它們的值在程序里面的block表達式內使用。在嵌套block里面,該值在最近的封閉范圍內被捕獲。
屬於同一作用域內並被__block修飾符標識的變量作為引用傳遞因此是可變的。
屬於同一作用域范圍內的block的變量就和函數的局部變量操作一樣。
example:使用本地非靜態變量
1 int multipiler = 7; 2 int (^myBlock)(int) = ^(int num){ 3 return multipiler * 7; 4 }; 5 如果在block體內修改multipiler的值將會發生錯誤。
13、可以通過__block修飾符指定引入的變量是可以更改的,此種類型的變量保存在變量共享的作用域范圍內,所有的blocks和block副本都聲明或者創建在和變量的作用域相同的范圍內。你可以指定引入一個變量為可更改的,即讀-寫的,通過應用__block存儲類型修飾符。局部變量的__block的存儲和register、auto、static等存儲類型相似,但它們之間不兼容。 __block變量保存在變量共享的作用域范圍內,所有的blocks和block副本都聲明或創建在和變量的作用域相同范圍內。所以,如果任何blocks副本聲明在棧內並未超出棧的結束時,該存儲會讓棧幀免於被破壞(比如封裝為以后執行)。同一作用域范圍內給定的多個block可以同時使用一個共享變量。 作為一種優化,block存儲在棧上面,就像blocks本身一樣。如果使用Block_copy拷貝了block的一個副本(或者在Objective-C里面給block發送了一條copy消息),變量會被拷貝到堆上面。所以一個__block變量的地址可以隨時間推移而被更改。
使用__block的變量有兩個限制:它們不能是可變長的數組,並且它們不能是包含有C99可變長度的數組變量的數據結構。
1 __block int x = 123; // x lives in block storage 2 void (^printXAndY)(int) = ^(int y) { 3 x = x + y; 4 printf("%d %d\n", x, y); 5 }; 6 printXAndY(456);
1 下面的例子顯示了blocks和其他幾個類型變量間的交互: 2 extern NSInteger CounterGlobal; 3 static NSInteger CounterStatic; 4 { 5 NSInteger localCounter = 42; 6 __block char localCharacter; 7 void (^aBlock)(void) = ^(void) { 8 ++CounterGlobal; 9 ++CounterStatic; 10 CounterGlobal = localCounter; // localCounter fixed at block creation 11 localCharacter = 'a'; // sets localCharacter in enclosing scope 12 }; 13 ++localCounter; // unseen by the block 14 localCharacter = 'b'; 15 aBlock(); // execute the block 16 17 // localCharacter now 'a' 18 }
14、在引用計數的環境里面,默認情況下當你在block里面引用一個Objective-C對象的時候,該對象會被retain。當你簡單的引用了一個對象的實例變量時,它同樣被retain。但是被__block存儲類型修飾符標記的對象變量不會被retain
注意:在垃圾回收機制里面,如果你同時使用__weak和__block來標識一個變量,那么該block將不會保證它是一直是有效的。 如果你在實現方法的時候使用了block,對象的內存管理規則更微妙:
如果你通過引用來訪問一個實例變量,self會被retain。
如果你通過值來訪問一個實例變量,那么變量會被retain
1 dispatch_async(queue, ^{ 2 // instanceVariable is used by reference, self is retained 3 doSomethingWithObject(instanceVariable); 4 }); 5 id localVariable = instanceVariable; 6 dispatch_async(queue, ^{ 7 // localVariable is used by value, localVariable is retained (not self) 8 doSomethingWithObject(localVariable); 9 });
15、通常你可以在block內使用C++的對象。在成員函數里面,通過隱式的導入this指針引用成員變量和函數,結果會很微妙。有兩個條件可以讓block被拷貝:
如果你擁有__block存儲的類,它本來是一個基於棧的C++對象,那么通常會使用copy的構造函數。
如果你在block里面使用任何其他C++基於棧的對象,它必須包含一個const copy的構造函數。該C++對象使用該構造函數來拷貝。
16、當你拷貝一個block時,任何在該block里面對其他blocks的引用都會在需要的時候被拷貝,即拷貝整個目錄樹(從頂部開始)。如果你有block變量並在該block里面引用其他的block,那么那個其他的block會被拷貝一份。 當你拷貝一個基於棧的block時,你會獲得一個新的block。但是如果你拷貝一個基於堆的block,你只是簡單的遞增了該block的引用數,並把原始的block作為函數或方法的返回值。
17、調用blocks
1>可以向函數一樣直接調用,如:blockName(arg1,arg2);
2>內嵌的方式使用,Cocoa Frameworks中使用了很多的blocks,如:
1 char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" }; 2 qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) { 3 char *left = *(char **)l; 4 char *right = *(char **)r; 5 return strncmp(left, right, 1); 6 });
3>作為方法的參數:
1 NSArray *array = [NSArray arrayWithObjects: @"A", @"B", @"C", @"A", @"B", @"Z",@"G", @"are", @"Q", nil]; 2 NSSet *filterSet = [NSSet setWithObjects: @"A", @"Z", @"Q", nil]; 3 BOOL (^test)(id obj, NSUInteger idx, BOOL *stop); 4 test = ^ (id obj, NSUInteger idx, BOOL *stop) { 5 if (idx < 5) { 6 if ([filterSet containsObject: obj]) { 7 return YES; 8 } 9 } 10 return NO; 11 }; 12 NSIndexSet *indexes = [array indexesOfObjectsPassingTest:test]; 13 NSLog(@"indexes: %@", indexes);
18、拷貝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)--除非是在垃圾回收的環境里面。
19、使用Blocks需要避免的模式:
一個block的文本(通常是^{...})是一個代表block的本地棧數據結構地址。因此該本地棧數據結構的作用范圍是封閉的復合狀態,所以你應該避免下面例子顯示的模式:
1 void dontDoThis() { 2 void (^blockArray[3])(void); // an array of 3 block references 3 for (int i = 0; i < 3; ++i) { 4 blockArray[i] = ^{ printf("hello, %d\n", i); }; 5 // WRONG: The block literal scope is the "for" loop 6 } 7 } 8
1 void dontDoThisEither() { 2 void (^block)(void); 3 int i = random(): 4 if (i > 1000) { 5 block = ^{ printf("got i at: %d\n", i); }; 6 // WRONG: The block literal scope is the "then" clause 7 } 8 // ... 9 }
20、調試Blocks
你可以在blocks里面設置斷點並單步調試。你可以在一個GDB的對話里面使用invoke-block來調用一個block。如下面的例子所示:
$ invoke-block myBlock 10 20
如果你想傳遞一個C字符串,你必須用引號括這它。例如,為了傳遞this string給doSomethingWithString的block,你可以類似下面這樣寫:
$ invoke-block doSomethingWithString "\"this string\""
21、定義接受Blocks的函數和方法的區別:
1 void useCodeBlock(NSComparisonResult (^theBlock)(NSString *str)){ 2 if(NSOrderedSame == theBlock(@"foo")){ 3 doSomeThingSame(); 4 }else{ 5 doSomethingElese(); 6 } 7 } 8 -(NSMutableArray*) useCodeBlock:(NSArray *) inArray withBlock:(BOOL (^)(NSInteger)) block{ 9 NSMutableArray *result = [NSMutableArray array]; 10 for(NSNumber *number in inArray){ 11 if(block([number integerValue])){ 12 [result addObject:number]; 13 } 14 } 15 return result; 16 }
22、所有標准的OC引用計數內存管理方法在代碼塊上都是用。但是塊是在站上分配的,所以對傳入代碼塊的獨享需要使用-copy而不是-retain,如果需要保留它則需要在堆上得到一個副本。
工作機制是,運行時會將代碼塊使用的任何外部變量和self對象都以敞亮的方式復制到堆上,這樣你就可以訪問那些變量以及所有的成員變量(代碼塊中創建的對象的成員變量)。任何通過__block指令標識的變量都會按位復制到堆上,代碼塊就需要負責與使用這些變量相關的其他內存管理工作。
23、通過typedef提高代碼快的可讀性

1 #import <Foundation/Foundation.h> 2 3 typedef void (^BlockWithCharArg) (char*); 4 @interface Person : NSObject 5 { 6 BlockWithCharArg myBlock; 7 } 8 -(void) doSomethinggWithBlock; 9 -(void) setMyBlock:(BlockWithCharArg) inBlock; 10 11 @end 12 13 14 #import "Person.h" 15 16 17 @implementation Person 18 19 -(void) doSomethinggWithBlock{ 20 myBlock("foo"); 21 } 22 -(void) setMyBlock:(BlockWithCharArg) inBlock{ 23 myBlock = [inBlock copy]; 24 } 25 -(void) dealloc{ 26 [myBlock release]; 27 [super release]; 28 } 29 @end