Blocks - 塊

來源于 Ry’s Objective-C Tutorial - RyPress

一個學習Objective-C基礎知識的網站.

個人覺得很棒,所以決定抽時間把章節翻譯一下.

本人的英語水平有限,有讓大家誤解或者迷惑的地方還請指正.

原文地址:http://rypress.com/tutorials/objective-c/blocks.html

僅供學習,如有轉摘,請注明出處.


塊(下文直接用Block)是OC中的匿名函數.允許你在對象之間傳遞任意的語句,這種方式往往比查閱(引用)某處定義的函數更直觀.此外,Block被實現成閉包,使它很容易捕獲其周圍(上下文)(其他變量)的狀態.

創建Block

Block的使用跟普通函數完全一樣.你可以聲明一個block變量,(這個過程)跟你聲明一個函數一樣,然后定義這個Block,也跟實現函數一樣,隨后調用這個Block,還是跟調用函數一樣:

// main.m
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Declare the block variable
        double (^distanceFromRateAndTime)(double rate, double time);
        
        // Create and assign the block
        distanceFromRateAndTime = ^double(double rate, double time) {
            return rate * time;
        };
        // Call the block
        double dx = distanceFromRateAndTime(35, 1.5);
        
        NSLog(@"A car driving 35 mph will travel "
              @"%.2f miles in 1.5 hours.", dx);
    }
    return 0;
}

()這個符號來標識distanceFromRateAndTime變量是一個block.跟函數聲明類似,你也需要包含返回值類型,參數類型以便編譯器能保證(強制)類型安全.與指針類型變量(比如,int *aPOinter)前的星號表現行為相似.^只在聲明block時使用,隨后你就可以跟使用其他普通變量一樣使用block.

block本身其實是一個沒有函數名的函數定義.上述的^double(double rate, double time)簽名解釋為:返回值為double類型,有兩個double類型的參數(返回類型可以被忽略,如果你想的話).在花括號{}內可以寫任意語句,這跟普通函數一樣.

在把block賦值給distanceFromRateAndTime變量后,我們就可以把這個變量當做函數來調用.

無參數的Blocks

如果一個block不帶任何參數,那么你可以忽略這個block中的參數列表.同樣的,block中的返回值始終是可選的,所以你可以把(block)定義(采用的符號)縮短成^ {}:

double (^randomPercent)(void) = ^ {
    return (double)arc4random() / 4294967295;
};
NSLog(@"Gas tank is %.1f%% full",
      randomPercent() * 100);

內置的arc4random()函數返回一個隨機的32位整型值.通過除以能返回的可能最大值(4294967295)來得到一個0到1之間的浮點值.

截止目前,block看起來就像一個更復雜的定義函數方式.而實際上是,它以閉包來實現,從而開啟了令人興奮的新編程機會的大門( But, the fact that they’re implemented as closures opens the door to exciting new programming opportunities).

閉包

在block內部,你有權訪問數據,包括局部變量,傳遞給bloclk的參數,以及全局變量/函數.但由于blocks以閉包實現,也就意味著你也有權訪問非局部變量.非局部變量是定義在block上下文中,而并不在block內部.比如,getFullCarName可以引用在block之前定義的make變量.

NSString *make = @"Honda";
NSString *(^getFullCarName)(NSString *) = ^(NSString *model) {
    return [make stringByAppendingFormat:@" %@", model];
};
NSLog(@"%@", getFullCarName(@"Accord"));    // Honda Accord

非局部變量實際上是被拷貝后與block一起當做常量存儲的,這就意味著它們是只讀的.如果嘗試著在block內給make變量賦新值,就會拋出一個編譯錯誤.

Accessing non-local variables as const copies
Accessing non-local variables as const copies

實際上,非局部變量被拷貝成常量意味著一個block不僅有權訪問這些非局部變量-它(還)創建了這些非局部變量的快照(副本).非局部變量在block定義時,就把它們自身攜帶的值凍結到這個block中了(Non-local variables are frozen at whatever value they contain when the block is defined),并且這個block始終用的都是被凍結(拷貝)的值,即使這個非局部變量隨后在程序中更改了.讓我們來一下,在創建block之后改變make變量發生了什么事情.

NSString *make = @"Honda";
NSString *(^getFullCarName)(NSString *) = ^(NSString *model) {
    return [make stringByAppendingFormat:@" %@", model];
};
NSLog(@"%@", getFullCarName(@"Accord"));    // Honda Accord

// Try changing the non-local variable (it won't change the block)
make = @"Porsche";
NSLog(@"%@", getFullCarName(@"911 Turbo")); // Honda 911 Turbo

閉包是一個非常方便的能與周圍狀態(上下文)一起工作的方式,因為它消除了將外部的值當做參數傳遞的需求-你只要簡單將非局部變量當做在這個block中定義的變量來用就行.

可變的非局部變量.

將非局部變量凍結(存儲)成常量值是一個安全的默認行為,以阻止你不經意的在block中修改這些變量(引起非局部變量值的更改).然而,也有你想在block中修改這些變量的情況.那么,你可以通過使用__block存儲修飾符來聲明一個變量以重寫它的const復制行為.

__block NSString *make = @"Honda";

這種(聲明方式)告訴block通過引用來捕獲這個變量,即,在block內部使用的make變量與其之外的使用的make變量之間創建一個直接關系.現在,你就可以在block外部給make賦新值,它會反映在block中,反之亦然.

Accessing non-local variables by reference
Accessing non-local variables by reference

像普通函數中的靜態局部變量一樣,__block修飾符可以充當多次調用block時使用的記憶體.This makes it possible to use blocks as generators(這句太蛋疼了,理解為把block當做一個生成器使用).例如下面的代碼段,它創建了一個可以在隨后多次執行中記住i值的block.

__block int i = 0;
int (^count)(void) = ^ {
    i += 1;
    return i;
};
NSLog(@"%d", count());    // 1
NSLog(@"%d", count());    // 2
NSLog(@"%d", count());    // 3

作為方法參數的Block

將block當做變量存儲并不常用,在實際情況下,blocks更多的被當做方法參數使用.它們被作為函數指針來解決同樣的問題[此處不是很明白這個same question指代的是什么],但因為它們被定義成內聯的,所以生成的代碼更易閱讀.

下面的例子中,Car接口聲明了一個記錄汽車行駛距離的方法.它不用強迫調用者必須傳遞一個速度常量,而是接收一個block,這個block利用時間來計算速度(the block defines the car’s speed as a function of time).

// Car.h
#import <Foundation/Foundation.h>

@interface Car : NSObject

@property double odometer;

- (void)driveForDuration:(double)duration
       withVariableSpeed:(double (^)(double time))speedFunction
                   steps:(int)numSteps;

@end

這個block的數據類型是double(^)(double time),標識著無論調用者傳遞什么樣的block,這個block的返回值必須是double類型,并且只接收單一的double類型參數.注意一點,此處的block跟該章節剛開始討論的block變量聲明基本是一樣的語法,除了沒有變量名.

實現文件中可以通過speedFunction來調用這個block.下面的例子使用單純的黎曼加法來近似得到持續時間中汽車行駛的距離(The following example uses a na?ve right-handed Riemann sum to approximate the distance traveled over duration).steps參數被用來讓調用者決定估算的精準度.

// Car.m
#import "Car.h"

@implementation Car

@synthesize odometer = _odometer;

- (void)driveForDuration:(double)duration
       withVariableSpeed:(double (^)(double time))speedFunction
                   steps:(int)numSteps {
    double dt = duration / numSteps;
    for (int i=1; i<=numSteps; i++) {
        _odometer += speedFunction(i*dt) * dt;
    }
}

@end

正如你在下面的main()函數中所見,block可以在方法調用中定義.盡管這需要時間解析這種語法,但是這比創建一個專用的函數來定義withVariableSped參數更直觀.

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car *theCar = [[Car alloc] init];
        
        // Drive for awhile with constant speed of 5.0 m/s
        [theCar driveForDuration:10.0
               withVariableSpeed:^(double time) {
                           return 5.0;
                       } steps:100];
        NSLog(@"The car has now driven %.2f meters", theCar.odometer);
        
        // Start accelerating at a rate of 1.0 m/s^2
        [theCar driveForDuration:10.0
               withVariableSpeed:^(double time) {
                           return time + 5.0;
                       } steps:100];
        NSLog(@"The car has now driven %.2f meters", theCar.odometer);
    }
    return 0;
}

這是block多功能用處的其中一個例子,標準框架中充滿了其他block的用例.NSArray允許使用帶有block的sortedArrayUsingComparator:方法來對元素進行排序,而UIView通過animateWithDuration:animations:方法中的一個block來決定動畫的最終狀態.

另外,NSOpenPanel類可以在用戶選中一個文件后執行一個block.這種便捷行為在[Ry’s Cocoa Tutorial]的[ Persistent Data]章節有探討.

定義Block類型

因為block的數據類型語法會讓你的方法變得混亂,所以typedef一個公用的block簽名很有用.如下面的代碼,創建了一個被稱作SpeedFunction的新類型,以便我們能在語義上更容易理解withVariable參數.

// Car.h
#import <Foundation/Foundation.h>

// Define a new type for the block
typedef double (^SpeedFunction)(double);

@interface Car : NSObject

@property double odometer;

- (void)driveForDuration:(double)duration
       withVariableSpeed:(SpeedFunction)speedFunction
                   steps:(int)numSteps;

@end

很多OC中的標準框架都使用這個技術(比如:NSComparator)

總結

Block提供了很多與C函數相同的功能,但是他們使用起來更直觀(在你習慣了它的語法后).由于他們可以被定義成內聯的,所以很容易在方法調用中使用,而且它們是閉包的,所以它們不費吹飛之力就可獲取周圍(上下文)的變量值.

下個模塊,我們安排一些有關iOS以及OS X的錯誤處理能力的內容.我們會探究兩種用來表示錯誤的類:NSException和NSError.


寫于15年09月11日 - 希望世界不再有恐怖襲擊

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容