Blocks塊編程指南

根據(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é):


<h1 id="10">開始使用塊</h1>
下面的部分將使用實例帶你開始學(xué)習(xí)塊

<h2 id="11">聲明、使用塊</h2>
我們使用 ^ 操作符去聲明一個塊變量的開始。塊主體使用{}(本例使用C語言,結(jié)尾使用;)

int multiplier = 7;

int (^myBlock)(int) = ^(int num) {

return num * multiplier;

};

下圖解釋此例。


blocks.jpg

注意,塊可以使用同一作用域內(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ù)來使用:

  1. 他們允許你編寫代碼的調(diào)用方法的上下文中執(zhí)行后實現(xiàn)。因此,框架中經(jīng)常使用塊作為參數(shù)。
  2. 它們允許訪問局部變量,而不是需要你在塊中帶著所有需要用到的變量。你可以直接用它訪問局部變量。

<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ù)

塊還支持其它兩種變量類型:

  1. 函數(shù)級別的__block變量。它可以在塊中改變值。
  2. const

下面定義了在塊中使用變量的規(guī)則:

  1. 可以訪問全部變量、靜態(tài)變量和局部變量。
  2. 參數(shù)是可以訪問的(類似于函數(shù)中得參數(shù))。
  3. 本地堆棧(非靜態(tài))變量封閉詞法作用域為常量變量捕獲。它們的值是在計劃內(nèi)的塊表達式。在嵌套塊,價值捕獲從最近的封閉范圍。
  4. 只有定義成__block的變量才可以在塊中被修改。
  5. 在塊中聲明局部變量和函數(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\""
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容