根據(jù)蘋果官方指南翻譯,最后加上點objective-c中得一些用法。
原文地址:Blocks Programming Topics
<h1 id="00">簡介</h1>
塊對象是C語言級別的語法和運行特性。類似于標準C函數(shù)。除了可執(zhí)行代碼之外他們還可以把變量綁定到堆(heap)棧(stack)中去。因此塊能維持一系列的狀態(tài)(data),這些狀態(tài)或數(shù)據(jù)會影響執(zhí)行后的結(jié)果。
你可以在API傳遞、多線程中使用塊函數(shù)表達式。最有用的是把塊作為回調(diào)函數(shù),因為在回調(diào)時可以攜帶代碼和參數(shù)。在OS X10.6的Xcode開發(fā)工具上可以使用塊,它使用GCC個Clang集成,你可以在OS X10.6以后或者IOS4.0以后的版本使用塊,塊運行時(block runtime)是開源的,可以在LLVM’s compiler-rt subproject repository中找到。塊之前在C標準工作組( N1370: Apple’s Extensions to C)中提到過。由于Objective-c和c++都由C衍生而來,所以塊都可以在這三種語言中使用。
你應(yīng)該通過閱讀這篇文檔來學(xué)習(xí)如何在C、C++、objective-c中使用塊。
<h2 id="01">文檔組成</h2>
這篇文檔包括以下章節(jié):
- 開始使用塊(包括了塊的快速、實用、簡介)
- 概念概述(包括了塊的概念簡介)
- 聲明和創(chuàng)建塊(包括了如何聲明一個塊變量和如何實現(xiàn)塊)
- 塊和變量(描述了塊和變量的作用,定義了__block類型修飾符)
- 使用塊(詳解各種用法)
<h1 id="10">開始使用塊</h1>
下面的部分將使用實例帶你開始學(xué)習(xí)塊
<h2 id="11">聲明、使用塊</h2>
我們使用 ^ 操作符去聲明一個塊變量的開始。塊主體使用{}(本例使用C語言,結(jié)尾使用;)
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
下圖解釋此例。
注意,塊可以使用同一作用域內(nèi)定義的變量
如果你聲明了一個塊變量,你可以把它當做函數(shù)來使用:
int multiple = 7;
int (^myBlock)(int) = ^(int num){
return num * multiple;
};
printf("%d", myBlock(3));
//prints "21"
<h2 id="12">直接使用塊</h2>
很多時候,我們并不需要直接聲明一個塊變量,你只需要提供一個內(nèi)聯(lián)塊來作為參數(shù),下例中使用了函數(shù) qsort_b,qsort_b函數(shù)和標準函數(shù)qsort_r相類似,但是它提供了一個塊來作為參數(shù)。
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, l);
});
//myCharacters is now { "Charles Consuming", "George", "TomJohn" }
<h2 id="13">Cocoa和塊</h2>
Cocoa frameworks有一些方法中使用塊作用參數(shù),典型的用在集合中執(zhí)行操作,或者用于操作完成后的回調(diào),下例展示了如何在NSArray的方法sortedArrayUsingComparator中使用塊,這個方法只有一個塊參數(shù),這樣塊就變成NSComparator的局部變量了。
NSArray *stringArray = @[ @"string 1",
@"string 21",
@"string 12",
@"string 11",
@"string 02" ];
static NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch | NSNumericSearch | NSWidthInsensitiveSearch | NSForceOrderingSearch;
NSLocale *currentLocale = [NSLocal 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",
)
*/
<h2 id="14">__block變量</h2>
塊的一個強大特性是可以改變同一作用域內(nèi)的變量,如果你聲明一個變量時使用__block,你就可以在塊的作用域內(nèi)改變它的值,下例中你可以使用block的變量來計算有多少個字符串值相同,在塊中使用currentLocale作為只讀變量。
NSArray *stringsArray = @[ @"string 1",
@"string 21", // <-
@"string 12",
@"string 11",
@"string 21", // <-
@"string 21", // <-
@"string 02" ];
NSLocale *currentLocale = [NSLocal 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:NSDiacriticInsenstiveSearch range:string1Range locale:currentLocale];
if (comparisonResult == NSOrderSame) {
orderSameCount++;
}
return comparisonResult;
}];
NSLog(@"diacriticInsensitiveSortArray: %@", diacriticInsensitiveSortArray);
NSLog(@"orderedSameCount: %d", orderedSameCount);
/*
Output:
finderSortArray: (
"string 1",
"string 02",
"string 11",
"string 12",
"string 21",
"str\U00eeng 21",
"stri\U00f1g 21"
)
orderSameCount: 2
*/
更多細節(jié)請看以下章節(jié) 聲明和創(chuàng)建塊
<h1 id="20">概念概述</h1>
塊在c以及c的衍生語言(Objective-c、c++等)中提供了一種特別的方式創(chuàng)建一個函數(shù)體來作為表達式,在其它語言環(huán)境里,塊有時被叫做“closure”(閉包)。
<h2 id="21">塊函數(shù)</h2>
一個塊在代碼里就是一個匿名的內(nèi)部集合:
- 像函數(shù)一樣有類型參數(shù)
- 有返回值
- 能捕獲定義范圍內(nèi)的狀態(tài)
- 能修改定義范圍內(nèi)的狀態(tài)
<h2 id="22">使用</h2>
塊通常非常小,作為獨立的代碼片段。它作為可并發(fā)執(zhí)行的封裝單元、集合中或者操作完成時的回調(diào)尤其有用。
塊有兩點原因作為回調(diào)函數(shù)來使用:
- 他們允許你編寫代碼的調(diào)用方法的上下文中執(zhí)行后實現(xiàn)。因此,框架中經(jīng)常使用塊作為參數(shù)。
- 它們允許訪問局部變量,而不是需要你在塊中帶著所有需要用到的變量。你可以直接用它訪問局部變量。
<h1 id="30">聲明和創(chuàng)建塊</h1>
<h2 id="31">聲明一個塊引用</h2>
聲明一個塊就和聲明一個函數(shù)一樣,除了塊使用 ^
而不是 *
,下面就是幾個正確聲明塊的方式:
void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
塊支持可變參數(shù)(...)。如果不帶參數(shù)需在參數(shù)列表里指定void。
塊被設(shè)計成完全類型安全給編譯器一套完整的元數(shù)據(jù)用于驗證使用的塊,參數(shù)傳遞給塊和賦值的返回值。你可以把一塊任意類型的指針引用,反之亦然。然而,你不能通過指針引用操作符(*)廢棄塊,這一塊的大小在編譯時無法計算。
你還可以給塊創(chuàng)建類型,在不同地方使用:
typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;
<h2 id="32">創(chuàng)建一個塊</h2>
我們用^
表明塊的開始,接著后面通過()
指定參數(shù)列表,{}
表明塊的內(nèi)容,下面定義了一個簡單地塊
float (^oneFrom)(float);
oneFrom = ^(float aFloat) {
float result = aFloat - 1.0;
return result;
};
如果你不確定塊的返回值,它可以進行自動類型推斷,如果你的參數(shù)列表是void
,你可以省略不寫。如果有多個返回語句,它們必須是同一類型。
<h2 id="33">全局塊</h2>
#import <stdio.h>
int GlobalInt = 0;
int (^getGlobalInt)(void) = ^{ return GlobalInt; };
<h1 id="40">塊和變量</h1>
<h2 id="41">變量的類型</h2>
這節(jié)描述了塊和變量的相互關(guān)系以及內(nèi)存管理。
在代碼塊中,可能有5種不同的方式處理變量。
你可以參考下面3種標準變量類型。
- 全局變量(包含靜態(tài)變量)
- 全局函數(shù)(技術(shù)上不是變量)
- 局部變量和參數(shù)
塊還支持其它兩種變量類型:
- 函數(shù)級別的__block變量。它可以在塊中改變值。
- const
下面定義了在塊中使用變量的規(guī)則:
- 可以訪問全部變量、靜態(tài)變量和局部變量。
- 參數(shù)是可以訪問的(類似于函數(shù)中得參數(shù))。
- 本地堆棧(非靜態(tài))變量封閉詞法作用域為常量變量捕獲。它們的值是在計劃內(nèi)的塊表達式。在嵌套塊,價值捕獲從最近的封閉范圍。
- 只有定義成__block的變量才可以在塊中被修改。
- 在塊中聲明局部變量和函數(shù)中一樣,
下面的例子說明了使用本地非靜態(tài)變量:
int x = 123;
void (^printXAndY)(int) = ^(int y) {
printf("%d %d\n", x, y);
};
printXAndY(456); // prints: 123 456
如上所述,要指定一個新值x在塊會導(dǎo)致一個錯誤:
int x = 123;
void (^printXAndY)(int) = ^(int y) {
x = x + y; // error
printf("%d %d\n", x, y);
};
允許一個變量被改變在一塊,使用__block存儲類型。
<h2 id="42">__block存儲類型</h2>
你可以指定變量為__block存儲類型
下面的例子說明了使用__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'
}
<h2 id="43">對象和塊變量</h2>
如果你在方法中使用一個塊,它創(chuàng)建了對對象的強引用:
- 如果你訪問實例變量的引用,強引用是
self
。 - 如果你訪問實例變量的值,強引用是這個變量。
下例是兩種不同的情況
dispatch_async(queue, ^{
// instanceVariable is used by reference, a strong reference is made to self
doSomethingWithObject(instanceVariable);
});
id localVariable = instanceVariable;
dispatch_async(queue, ^{
/*
localVariable is used by value, a strong reference is made to localVariable(and not to self).
*/
doSomethingWithObject(localVariable);
});
<h1 id="50">使用塊</h1>
<h2 id="51">調(diào)用塊</h2>
如果你聲明了一個塊變量,你可以再函數(shù)中使用:
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
<h2 id="52">使用塊作為函數(shù)參數(shù)</h2>
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" }
我們注意到塊中包含的函數(shù)的參數(shù)列表。
下例說明了如何在dispatch_apply
函數(shù)中使用塊。dispatch_apply
聲明如下:
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
提交一個功能塊調(diào)度隊列的多個調(diào)用。需要三個參數(shù),第一個參數(shù)指定數(shù)量的迭代執(zhí)行;第二個指定隊列提交的塊;第三是塊本身,進而需要一個單一的參數(shù)當前索引的迭代。
您可以使用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);
});
<h2 id="53">使用塊作為方法參數(shù)</h2>
NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];
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)]
*/
<h2 id="54">塊復(fù)制</h2>
Block_copy();
Block_release();
<h2 id="55">避免這些模式</h2>
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.
}
// ...
}
<h2 id="56">Debug</h2>
你可以設(shè)置斷點和單步成塊。您可以調(diào)用一個塊中使用invoke-block GDB會話,如本例中所示:
$ invoke-block myBlock 10 20
如果你想通過一個C字符串,你必須引用它。例如,通過這個字符串到doSomethingWithString塊中,你將編寫以下:
$ invoke-block doSomethingWithString "\"this string\""