Monday, November 12, 2012

塊程式設計指南

塊程式設計指南  

2012-06-05 11:20:12| 分類: iPhone | 標籤: ^ 塊程式設計 |字型大小 訂閱
——譯自Apple Reference Library《Blocks Programming Topic》
摘自:kmyhy簡介
塊物件是C語言的句法和運行時特性。它類似于標準C函數,但可以將代碼、變數綁定到堆(heap)、棧(stack)。一個塊還維護了一系列的狀態,這些狀態或資料影響著執行的結果。
可以把塊組成函數運算式,用於傳遞給API,或者使用在多執行緒裡。最有用的是回檔,因為塊在回檔時能把代碼和資料一起傳送。
OSX 10.6的Xcode中,可以使用塊,它隨GCC和 Clang 一起集成。OSX 10.6及iOS 4.0以後支援塊語法。 塊運行時是開源的,它能被集成到 LLVM’s compiler-rt subproject repository中。標準C工作組的 N1370: Apple’s Extensions to C ( 其中也包括垃圾回收 )對塊進行了定義。O-C和C++都來自于C,塊在3種語言(包括O-C++)都能工作。
這篇文檔中,你會學習到什麼是塊物件,以及怎樣在C,C++和O-C中使用它,使代碼的性能和可維護性更高。
開始
聲明塊
^ 操作符聲明一個塊變數的開始(跟C一樣用; 來表示運算式結束),如代碼所示:
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
    return num * multiplier;
};
解釋 :


注意,塊可以使用同一作用域內定義的變數。
 
一旦聲明瞭塊,你可以象使用函數一樣調用它:
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
    return num * multiplier;
};
printf("%d", myBlock(3));
直接使用塊
很多情況下,你不必聲明塊變數,而簡單地寫一個行內塊並把它當作一個參數,如下面的代碼所示。
gsort_b類似標準的 gsort_r 函數,但它最後一個參數是一個塊。
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
 
    char *left = *(char **)l;
 
    char *right = *(char **)r;
 
    return strncmp(left, right, 1);
 
});
// myCharacters is now { "Charles Condomine", "George", TomJohn" }
Cocoa 和塊
Cocoa框架中,有幾種把塊作為參數的方法。典型的是在集合中進行一個操作,或者在操作完成後作為一個回檔。下列代碼顯示如何在NSArray的sortedArrayUsingComparator方法中使用塊。這個方法使用了一個塊參數。為了演示,在這裡把塊定義為一個NSComparator本地變數。
 
NSArray *stringsArray = [NSArray arrayWithObjects: @"string 1", @"String 21",@"string 12",
@"String 11", @"String 02", nil];
static NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch | NSNumericSearch |
        NSWidthInsensitiveSearch | NSForcedOrderingSearch;
NSLocale *currentLocale = [NSLocale currentLocale];
NSComparator finderSortBlock = ^(id string1, id string2) {
    NSRange string1Range = NSMakeRange(0, [string1 length]);
    return [string1 compare:string2 options:comparisonOptions range:string1Range locale:currentLocale];
};
NSArray *finderSortArray = [stringsArray sortedArrayUsingComparator:finderSortBlock];
NSLog(@"finderSortArray: %@", finderSortArray);
 
/*Output:
finderSortArray: (
    "string 1",
    "String 02",
    "String 11",
    "string 12",
    "String 21"
)*/
塊變數
塊的一個強大功能它可以改變在同一作用域內的變數。__block修飾符來標識一個變數能夠被塊改變。使用下面的代碼,你可以用一個塊變數計算進行比較的字串中有多少是相同的。為了演示,塊是直接使用的,同時currentLocal變數對於塊來說是唯讀的。
NSArray *stringsArray = [NSArray arrayWithObjects:
@"string 1", @"String 21", // <-
  @"string 12", @"String 11",@"Str?ng 21", // <-
  @"Stri?g 21", // <-
@"String 02", nil];
NSLocale *currentLocale = [NSLocale currentLocale];
__block NSUInteger orderedSameCount = 0;
NSArray *diacriticInsensitiveSortArray = [stringsArray sortedArrayUsingComparator:^(id string1, id string2) {
    NSRange string1Range = NSMakeRange(0, [string1 length]);
    NSComparisonResult comparisonResult = [string1 compare:string2 options:NSDiacriticInsensitiveSearch range:string1Range locale: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
*/
相關概念
塊提供了一種方法,允許你創建一種特殊的函數體,在C及C派生語言如O-C和C++中,可以把塊視為運算式。其他語言中,為了不與C術語中的塊混淆,塊也被稱作closure(國內譯作閉包),這裡它們都稱做blocks。
塊的功能
 
塊是行內的代碼集合:
?        同函數一樣,有類型化參數清單
?        有返回結果或者要申明返回類型
?        能獲取同一作用域(定義塊的相同作用域)內的狀態
?        可以修改同一作用域的狀態(變數)
?        與同一範圍內的其他塊同享變數
?        在作用域釋放後能繼續共用和改變同一範圍內的變數
甚至可以複製塊並傳遞到其他後續執行的執行緒。編譯器和運行時負責把所有塊引用的變數保護在所有塊的拷貝的生命週期內。對於C和C++,塊是變數,但對於O-C ,塊仍然是物件。
塊的使用
塊通常代表小段的、自包含的代碼片段。
因此,它們封裝為可以並存執行的工作單元額外有用,要麼用於在集合中進行遍歷,要麼在其他操作完成使作為回檔。
塊代替傳統回呼函數的意義有兩個:
1. 它們允許在方法實現的調用中就近地寫入代碼。而且塊經常被作為框架中一些方法的參數。
2. 它們允許訪問本地變數。在進行執行緒操作時,相比回呼函數需要把所需的上下文資訊植入資料結構中而言,塊直接存取本地變數顯然更加簡單。
 
塊的聲明和創建
聲明塊變數
塊變數引用了塊。它的聲明語法類似函數指標,除了需要使用^代替*。
void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
塊支援可變參數(...)。如果塊沒有參數,則必需使用void來代替整個參數清單。
塊是型別安全的,通過設置編譯選項,編譯器會檢查塊的調用、參數和返回類型。可以把塊變數轉換為指標類型,但不能使用*對其解除引用——塊的長度在編譯時無法確定。
可以創建一個塊類型,這樣你就可以把塊當作一個可以反復多次使用的符號:
typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;
創建塊
塊以^開始,以;結束。下面顯示了塊的定義:
int (^oneFrom)(int);
oneFrom = ^(int anInt) {
    return anInt - 1;
};
如果未顯式地聲明塊的傳回值類型,可能會自動從塊代碼中推斷返回類型。如果參數清單為void,而且返回類型依靠推斷,你可以省略參數清單的void。否則,當塊中存在return語句時,它們應當是精確匹配的(可能需要必要的類型轉換)。
全域塊
可以把塊定義為全域變數,在檔級別上使用。
#import <stdio.h>
int GlobalInt = 0;
int (^getGlobalInt)(void) = ^{ return GlobalInt; };
 
塊和變數
本節描述塊和變數之間的交互,包括記憶體管理。
變數類型
在塊代碼內部,變數會被處理為5種不同情況。
就像函數一樣,可以引用3種標準的變數:
?        全域變數,包括靜態變數
?        全域函數
?        本地變數及參數(在塊範圍內)
此外塊還支援兩種變數:
1. 在函數級別,是__block變數。它們在塊範圍內是可變的,如果所引用的塊被覆制到堆後,它們也是被保護的。
2. const imports.
在方法體內,塊還可以引用O-C 執行個體變數,見 物件和塊變數 」.
在塊中使用變數有以下規則:
1. 可訪問在同一範圍內的全域變數包括靜態變數。
2. 可以訪問傳遞給塊的參數(如同函數參數)。
3. 同一範圍的棧(非static)變數視作const變數。它們的值類似塊運算式。嵌套塊時,從最近的作用域取值。
4. 在同一範圍內聲明的變數,如果有__block修飾符修飾,則值是可變的。在該範圍內包括同一範圍內的其他塊對該變數的改變,都將影響該作用域。具體見「__block 存儲類型」。
5. 在塊的範圍內(塊體)聲明的本地變數,類似于函數中的本地變數。塊的每次調用都會導致重新拷貝這些變數。這些變數可作為const或參考(by-reference)變數。
下面演示本地非靜態變數的使用:
int x = 123;
void (^printXAndY)(int) = ^(int y) {
printf("%d %d/n", x, y);
};
printXAndY(456); // prints: 123 456
注意,試圖向x進行賦值將導致錯誤:
int x = 123;
  void (^printXAndY)(int) = ^(int y) {
  x = x + y; // error
printf("%d %d/n", x, y);
};
要想在塊內改變x的值,需要使用__block修飾x。見「__block存儲類型」。
__block 存儲類型
你可以規定一個外部的變數是否可變——可讀寫——通過使用__block存儲類型修飾符。__block存儲類似但不同于register,auto和static存儲類型。
__block變數在變數聲明的作用域、所有同一作用域內的塊,以及塊拷貝之間同享存儲。而且這個存儲將在棧幀(stack frame)釋放時得以保留,只要同一幀內申明的塊的拷貝仍然存活(例如,被入棧以便再次使用)。在指定作用域內的多個塊能同時使用共用變數。
作為一種優化,塊存儲使用棧存儲,就如同塊自身一樣。如果使用Block_copy拷貝塊(或者在O-C向塊發送copy消息),變數被拷貝到堆裡。而且,__block變數的位址隨後就會改變。
__block變數有兩個限制:不能是可變長度的陣列,也不能是包含C99可變長度陣列的結構體。
下面顯示了__block變數的使用:
__block int 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
下面顯示了在塊中使用多種類型的變數:
extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
NSInteger localCounter = 42;
__block char 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'
}
物件和塊變數
塊提供了對O-C和C++物件的支援 。
O-C物件
在引用計數的情況下,當你在塊中引用一個O-C物件,物件會被retained。甚至只是簡單引用這個物件的執行個體變數,也是一樣的。
但對於__block標記的物件變數,就不一樣了。
注意:在垃圾回收的情況下,如果同時用__weak和__block修飾變數,塊可能不一定保證它是可用 的。
如果在方法體中使用塊,物件執行個體變數的記憶體管理規則 比較微妙:
?        如果通過物件引用方式訪問執行個體變數,self retained;
?        如果通過值引用方式訪問執行個體變數,變數是retained;
下面代碼演示了這2種情況:
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);
});
C++ 物件
一般,可以在塊中使用C++物件。在成員函數中對成員變數進行引用,儼然是對指標的引用,可以對其進行改變。如果塊被拷貝,有兩種結果:
如果有__block存儲類型的類,該類是基於棧的C++物件,通常會使用複製建構函式;
如果使用了其他塊中的基於棧的C++物件,它必需有一個const的複製建構函式。該C++物件使用該建構函式進行拷貝。
拷貝塊時,其引用的其它塊可能也被拷貝(從頂部開始)。如果有塊變數,並且在這個塊中引用了一個塊,那個塊也會被拷貝。
拷貝一個基於棧的塊時,你得到的是新的塊。拷貝一個基於堆的塊時,只是簡單的增加了retain數,然後把copy方法/函數的結果返回這個塊。
 
 
使用塊
塊的調用
如果把塊申明為變數,可以把它當成函數使用,例如:
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
但時常會將塊以參數形式傳遞給一個函數或方法,這樣,就會使用行內(inline)塊。
把塊作為函數參數
在這種情況下,不需要塊申明。簡單地在需要把它作為參數的地方實現它就行。如下所示,gsort_b是一個類似標準gsort_r的函數,它的最後一個參數使用了塊。
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
char *left = *(char **)l;
char *right = *(char **)r;
return strncmp(left, right, 1);
});
// Block implementation ends at "}"
 
 
// myCharacters is now { "Charles Condomine", "George", TomJohn" }
注意,塊包含在函數的參數清單中。
接下來的例子顯示如何在dispath_apply函數中使用塊。dispatch_apply的聲明是:
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
這個函數把塊提交給dispatch佇列以進行調用。它有3個參數:要操作的次數;塊被提交到的佇列;塊——這個塊有一個參數——遍歷操作的當前次數。
可以用dispatch_apply簡單地列印出遍歷操作的索引:
#include <dispatch/dispatch.h>
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);
});
把塊作為參數使用
Cocoa提供了大量使用塊的方法。把塊作為參數使用與使用其他類型的參數並無不同。
以下代碼判斷陣列中前5個元素中含有給定filter集合的索引。
NSArray *array = [NSArray arrayWithObjects: @"A", @"B", @"C", @"A", @"B", @"Z",@"G", @"are", @"Q", nil];
NSSet *filterSet = [NSSet setWithObjects: @"A", @"Z", @"Q", nil];
BOOL (^test)(id obj, NSUInteger idx, BOOL *stop);
test = ^ (id obj, NSUInteger idx, BOOL *stop) {
if (idx < 5) {
if ([filterSet containsObject: obj]) {
return YES;
}
}
return NO;
};
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:test];
NSLog(@"indexes: %@", indexes);
/*Output:
indexes: <NSIndexSet: 0x10236f0>[number of indexes: 2 (in 2 ranges), indexes: (0 3)]
*/
以下代碼判斷一個NSSet物件中是否包含指定的本地變數,如果是的話把另一個本地變數(found)設置為YES(並停止搜索)。注意found被聲明為__block變數,塊是在行內聲明的:
__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
 
塊複製
一般,你不需要複製塊。只有當你希望在這個塊申明的範圍外使用它時需要複製它。複製將導致塊移動到堆中。
可以使用C函數釋放和複製塊。
Block_copy();
Block_release();
對於O-C,則可向塊發送copy,retain和release(以及autorelease)消息。
為避免記憶體洩露,一個Block_copy()總是對應一個Block_release()。每個copy/retain總是有對應的release(或autorelease)——使用垃圾回收則例外。
避免的用法
一個塊聲明(即^{...})是一個本地棧式資料結構(stack-local data structure)的位址,這個位址就代表了塊。本地棧式資料結構是{}圍住的複合陳述式,因此應該避免如下用法:
void dontDoThis() {
void (^blockArray[3])(void);// 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
}
// ...
}
 
調試
可以在塊內設置中斷點,並進行單步調試。GDB會話中,使用invoke-block調用塊,比如:
$ invoke-block myBlock 10 20
如果需要傳遞C字串,必需用雙引號把它引住。例如,向doSomethignWithString塊傳遞一個字串:
$ invoke-block doSomethingWithString "/"this string/""
 

No comments: