iOS4引入了一個(gè)新特性,支持代碼塊的使用,,
這將從根本上改變你的編程方式,。代碼塊是對(duì)C語言的一個(gè)擴(kuò)展,因此在Objective-C中完全支持,。如果你學(xué)過Ruby,Python或Lisp編程 語言,那么你肯定知道代碼塊的強(qiáng)大之處,。簡單的說,,你可以通過代碼塊封裝一組代碼語句并將其當(dāng)作一個(gè)對(duì)象。代碼塊的使用是一種新的編碼風(fēng)格,,可以讓你運(yùn)用 自如的使用iOS4中新增API,。
我們先來看兩個(gè)在iOS4中使用代碼塊的例子(你很有可能已經(jīng)見過):view animations 和enumeration
使用代碼塊的例子
第一個(gè)例子,假設(shè)我們創(chuàng)建一個(gè)紙牌游戲,,需要展現(xiàn)紙牌被派發(fā)到玩家面前的動(dòng)畫效果,。幸運(yùn)的是通過UIKit框架可以很容易的實(shí)現(xiàn)一個(gè)動(dòng)畫效果。但是最終是什么樣的動(dòng)畫是由你的程序決定的,。你可以在代碼塊中指定動(dòng)畫的內(nèi)容然后再將代碼塊傳給animateWithDuration:animations:方法,,像下面這樣:
[UIView animateWithDuration:2.0
animations:^ {
self.cardView.alpha = 1.0;
self.cardView.frame = CGRectMake(176.0, 258.0, 72.0, 96.0);
self.cardView.transform = CGAffineTransformMakeRotation(M_PI);
}
];
當(dāng)這個(gè)動(dòng)畫代碼塊執(zhí)行時(shí),我們的紙牌會(huì)展現(xiàn)三種方式的動(dòng)畫:改變它的alpha值從而淡入顯示,,改變它的位置到右下角(玩家的位置),,以及自轉(zhuǎn)180度(為了使其效果更好)。
第二個(gè)代碼塊的例子是迭代一個(gè)紙牌的集合,,并打印其名字和在集合里的索引值,。
你可以通過使用for循環(huán)來達(dá)到目的,但是在iOS4中NSArray類有一個(gè)使用了代碼塊的方便方法:enumerateObjectsUsingBlock:,。下面是如何使用它:
NSArray *cards = [NSArray arrayWithObjects:@"Jack", @"Queen", @"King", @"Ace", nil];
[cards enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
NSLog(@"%@ card at index %d", object, index);
}];
這個(gè)代碼塊使用了三個(gè)參數(shù):數(shù)組中的一個(gè)對(duì)象,,該對(duì)象的索引,以及一個(gè)標(biāo)識(shí)迭代是否結(jié)束的標(biāo)志,。我們稍候再對(duì)其進(jìn)一步探討,。enumerateObjectsUsingBlock: 這個(gè)方法會(huì)將集合中的每一個(gè)元素傳入相應(yīng)的參數(shù)并調(diào)用代碼塊中的方法,。
因此在你的mac和iOS程序中使用代碼塊的優(yōu)勢(shì)是:它允許你附加任意的代碼到蘋果官方提供的方法上。盡管在概念上與代理相似,,但是在方法中使用簡短的內(nèi)聯(lián)代碼塊往往更加方便,,更加優(yōu)雅。
這是一個(gè)好的開始,,但重要的是要明白它內(nèi)部的處理,。當(dāng)我學(xué)習(xí)新東西的時(shí)候,我喜歡先將其分為一個(gè)個(gè)簡單的部分,,了解它們?nèi)绾喂ぷ?,然后再將它們組裝到一塊,這樣我會(huì)對(duì)自己寫的代碼以及快速解決出現(xiàn)的問題充滿信心,。因此,,讓我們先回頭學(xué)習(xí)下如何聲明和調(diào)用簡單的代碼塊。
代碼塊的基本概念
一個(gè)代碼塊可以簡單看作是一組可執(zhí)行的代碼,。例如,,下面是一個(gè)打印當(dāng)前日期和時(shí)間的代碼塊:
^ {
NSDate *date = [NSDate date];
NSLog(@"The date and time is %@", date);
};
插 入符號(hào)(^)聲明一個(gè)代碼塊的開始,一對(duì)大括號(hào){}構(gòu)成了代碼塊的體部,。你可以認(rèn)為代碼塊與一個(gè)匿名函數(shù)類似,。那么,如果是一個(gè)匿名的函數(shù),,我們?cè)撛趺凑{(diào) 用這個(gè)代碼塊呢,?最常見使用代碼塊的方式是將其傳入方法中供方法回調(diào),就像之前我們已經(jīng)見到了view animations 和enumeration,。另一種使用代碼塊的方式是將其賦予代碼塊變量,,然后可使用該變量來直接調(diào)用代碼塊。以下是如何聲明我們的代碼塊并將它賦予代碼 塊變量now:
void (^now)(void) = ^ {
NSDate *date = [NSDate date];
NSLog(@"The date and time is %@", date);
};
聲明一個(gè)塊變量的語法需要一些時(shí)間適應(yīng),,這才有趣,。如果你使用過函數(shù)指針,代碼塊變量與其類似,。在上面代碼等號(hào)右邊是我們已經(jīng)介紹過的代碼塊,。等號(hào)左邊我們聲明了一個(gè)代碼塊變量now。
代碼塊變量之前有^符號(hào)并被小括號(hào)包著,,代碼塊變量有類型定義的,。因此,上圖中的now變量可以應(yīng)用任何無參,,無返回值的代碼塊,。我們之前聲明的代碼塊符合這要求,,所以我們可以放心的把它分配給now變量,。
只要有一個(gè)代碼塊變量,,并在其作用域范圍內(nèi),我們就可以像調(diào)用函數(shù)一樣來調(diào)用它,。下面是如何調(diào)用我們的代碼塊:
now();
你可以在C函數(shù)或者Objective-c方法中聲明代碼塊變量,,然后在同一作用域內(nèi)調(diào)用它,就像我們前面說明那樣,。當(dāng)代碼塊執(zhí)行時(shí),,它打印當(dāng)前的日期和時(shí)間。目前為止,,進(jìn)展順利,。
代碼塊是閉包
如 果這就是代碼塊的全部的話,那么他與函數(shù)是完全相同的,。但事實(shí)是代碼塊不僅僅是一組可執(zhí)行的代碼,。代碼塊能夠捕捉到已聲明的同一作用域內(nèi)的變量,同時(shí)由于 代碼塊是閉包,,在代碼塊聲明時(shí)就將使用的變量包含到了代碼塊范圍內(nèi),。為了說明這一點(diǎn),讓我們改變一下前面的例子,,將日期的初始化移到代碼塊之外,。
NSDate *date = [NSDate date];
void (^now)(void) = ^ {
NSLog(@"The date and time is %@", date);
};
now();
當(dāng)你第一次調(diào)用這個(gè)代碼塊的時(shí)候,它與我們之前的版本結(jié)果完全一致:打印當(dāng)前的日期和時(shí)間,。但是當(dāng)我們改變?nèi)掌诤笤僬{(diào)用代碼塊,那么就會(huì)有顯著的不同了,,
sleep(5);
date = [NSDate date];
now();
盡 管我們?cè)谡{(diào)用代碼塊之前改變了日期,,但是當(dāng)代碼塊調(diào)用時(shí)仍然打印的是之前的日期和時(shí)間。就像是日期在代碼塊聲明時(shí)停頓了一樣,。為什么會(huì)這樣呢,,當(dāng)程序執(zhí)行 到代碼塊的聲明時(shí),代碼塊對(duì)同一作用域并且塊內(nèi)用到的變量做一個(gè)只讀的備份,。你可以認(rèn)為變量在代碼塊內(nèi)被凍結(jié)了,。因此,不論何時(shí)當(dāng)代碼塊被調(diào)用時(shí),,立即調(diào) 用或5秒鐘之后,,只要在程序退出之前,它都是打印最初的日期和時(shí)間,。
事實(shí)上,,上面那個(gè)展示代碼塊是閉包的例子并不十分完善,畢竟,你可以將日期作為一個(gè)參數(shù)傳入到代碼塊中(下面講解),。但是當(dāng)你將代碼塊在不同方法間傳遞時(shí)閉包的特性就會(huì)變得十分有用,,因?yàn)樗锩娴淖兞渴潜3植蛔兊摹?br style="margin-top:0px; margin-right:0px; margin-bottom:0px; margin-left:0px; padding-top:0px; padding-right:0px; padding-bottom:0px; padding-left:0px">
代碼塊參數(shù)
就像函數(shù)一樣,代碼塊可以傳入?yún)?shù)和返回結(jié)果,。例如,,我們想要一個(gè)能夠返回指定數(shù)的三倍的代碼塊,下面是實(shí)現(xiàn)的代碼塊:
^(int number) {
return number * 3;
};
為代碼塊聲明一個(gè)變量triple,,如下:
int (^triple)(int) = ^(int number) {
return number * 3;
};
上面說過,,我們需要熟悉等號(hào)左邊聲明代碼塊變量的語法。現(xiàn)在讓我們從左到右分開來說明:
最 左邊的int是返回值類型,,中間是小括號(hào)包圍插入符號(hào)^及代碼塊變量的名字,,最后又一個(gè)小括號(hào),包圍著參數(shù)的類型(上面例子中只有一個(gè)int參數(shù)),。等號(hào) 右邊的代碼塊聲明必須符合左側(cè)的定義,。有一點(diǎn)要說明的是,為了方便,,可以不聲明代碼塊的返回類型,,編譯器會(huì)從返回語句中做出判斷。
要調(diào)用這個(gè)代碼塊,,你需要傳入一個(gè)需要乘3的參數(shù),,并接受返回值,像這樣:
int result = triple(2);
下面你將知道如何聲明并創(chuàng)建一個(gè)需要兩個(gè)int型參數(shù),,將它們相乘然后返回結(jié)果的代碼塊:
int (^multiply)(int, int) = ^(int x, int y) {
return x * y;
};
這是如何調(diào)用這個(gè)代碼塊:
int result = multiply(2, 3);
聲明代碼塊變量使我們有機(jī)會(huì)探討代碼塊類型以及如何調(diào)用,。代碼塊變量類似函數(shù)指針,調(diào)用代碼塊與調(diào)用函數(shù)相似,。不同于函數(shù)指針的是,,代碼塊實(shí)際上是Objective-C對(duì)象,這意味著我們可以像對(duì)象一樣傳遞它們,。
調(diào)用代碼塊的方法
在實(shí)際中,,代碼塊經(jīng)常被作為參數(shù)傳入方法中供其回調(diào)。當(dāng)把代碼塊作為一個(gè)參數(shù)時(shí),,相比分配一個(gè)代碼塊變量,,更通常的做法是作為內(nèi)聯(lián)代碼塊。例如,,我們之前看到的例子:view animations 和enumeration,。
蘋 果官方已經(jīng)增加了一些使用代碼塊的方法到他們的框架中。你也可以寫一些使用代碼塊的API了,。例如,,我們要?jiǎng)?chuàng)建一個(gè)Worker類的使用代碼塊的類方法,, 該方法重復(fù)調(diào)用代碼塊指定的次數(shù),并處理代碼塊每次返回的結(jié)果,。下面是我們使用內(nèi)聯(lián)代碼塊調(diào)用這個(gè)方法,,代碼塊負(fù)責(zé)返回1到10的每個(gè)數(shù)的三倍,。
[Worker repeat:10 withBlock:^(int number) {
return number * 3;
}];
這個(gè)方法可以將任何接受一個(gè)int型參數(shù)并返回一個(gè)int型結(jié)果的代碼塊作為參數(shù),,如果想得到數(shù)字的二倍,,只需要改變傳入方法的代碼塊,。
編寫使用代碼塊的方法
在第一部分我們留下了一個(gè)任務(wù):寫一個(gè)Work類的調(diào)用代碼塊的類方法,并且重復(fù)調(diào)用代碼塊指定的次數(shù),,還要處理每次代碼塊的返回值,。如果我們想要得到1到5的三倍的話,,那么下面是我們?cè)撊绾握{(diào)這個(gè)帶有內(nèi)聯(lián)代碼塊的方法:
[Worker repeat:5 withBlock:^(int number) {
return number * 3;
}];
我 經(jīng)常這樣設(shè)計(jì)一個(gè)類,首先寫代碼調(diào)用一個(gè)虛構(gòu)的方法,,這也是在提交之前一種形成API的簡單方式,,一旦認(rèn)為這個(gè)方法調(diào)用正確,我就去實(shí)現(xiàn)這個(gè)方法。這樣,, 那個(gè)方法的名字是repeat:withBlock:,,我認(rèn)為不合適(我知道在第一部分是叫這個(gè)名字,但我已經(jīng)改變注意了),。這個(gè)名字容易使人混淆,,因?yàn)?該方法實(shí)際上并不是重復(fù)做相同的事情。這個(gè)方法從1迭代到指定的次數(shù),,并處理代碼塊的返回,。所以讓我們開始正確的重命名它:
[Worker iterateFromOneTo:5 withBlock:^(int number) {
return number * 3;
}];
我對(duì)這個(gè)使用兩個(gè)參數(shù)的方法的名字iterateFromOneTo:withBlock:很滿意,一個(gè)int型參數(shù)表示調(diào)用代碼塊的次數(shù)和一個(gè)要被調(diào)用的代碼塊參數(shù)?,F(xiàn)在讓我們?nèi)?shí)現(xiàn)這個(gè)方法,。
對(duì) 于初學(xué)者,我么該如何聲明這個(gè) iterateFromOneTo:withBlock:方法呢,?首先我們需要知道所有參數(shù)的類型,,第一個(gè)參數(shù)很容易,是個(gè)int類型,;第二個(gè)參數(shù)是一個(gè) 代碼塊,代碼塊是有返回類型的,。在這個(gè)例子中,,這個(gè)方法可以接受任何有一個(gè)int型參數(shù)并返回int型結(jié)果的代碼塊作為參數(shù)。下面是實(shí)際的代碼塊類型:
int (^)(int)
已經(jīng)有了方法的名字和它的參數(shù)類型,,我們就可以聲明這個(gè)方法了,。這是Worker類的類方法,我們?cè)趙orker.h中聲明它:
@interface Worker : NSObject {
}
+ (void)iterateFromOneTo:(int)limit withBlock:(int (^)(int))block;
@end
第 一眼看去,,代碼塊參數(shù)不容易理解,。有個(gè)要記住訣竅是:在Objective-C中所有的方法參數(shù)有兩個(gè)部分組成。被括起來的參數(shù)類型以及參數(shù)的名稱。這個(gè) 例子中,,參數(shù)的要求是一個(gè)是int型和一個(gè)是int(^)(int)型的代碼塊(你可以為參數(shù)命名為任意的名字,,不一定非得是block)。這個(gè)方法的實(shí) 現(xiàn)是在Worker.m文件文件中,,比較簡單:
#import "Worker.h"
@implementation Worker
+ (void)iterateFromOneTo:(int)limit withBlock:(int (^)(int))block {
for (int i = 1; i <= limit; i++) {
int result = block(i);
NSLog(@"iteration %d => %d", i, result);
}
}
@end
方 法通過一個(gè)循環(huán)來每次調(diào)用代碼塊,,并打印出代碼塊的返回結(jié)果。記住一旦我們?cè)谧饔糜騼?nèi)有一個(gè)代碼塊變量,,那么就可以像函數(shù)一樣使用它,。在這里代碼塊參數(shù)就 是一個(gè)代碼塊變量。因此,,當(dāng)執(zhí)行block(i)時(shí)就會(huì)調(diào)用傳入的代碼塊,。當(dāng)代碼塊返回結(jié)果后會(huì)繼續(xù)往下執(zhí)行。現(xiàn)在我們可以使用內(nèi)聯(lián)代碼塊的方式調(diào)用 iterateFromOneTo:withBlock:方法,,像這樣:
[Worker iterateFromOneTo:5 withBlock:^(int number) {
return number * 3;
}];
我們也可以不使用內(nèi)聯(lián)代碼塊的方式,,傳入一個(gè)代碼塊變量作為參數(shù):
int (^tripler)(int) = ^(int number) {
return number * 3;
};
[Worker iterateFromOneTo:5 withBlock:tripler];
不論那種方式,我們得到的輸出如下:
iteration 1 => 3
iteration 2 => 6
iteration 3 => 9
iteration 4 => 12
iteration 5 => 15
當(dāng)然我們可以傳入進(jìn)行任何運(yùn)算的代碼塊,。想要得到數(shù)字的平方嗎,?沒問題,只要傳入一個(gè)不同的代碼塊:
[Worker iterateFromOneTo:5 withBlock:^(int number) {
return number * number;
}];
現(xiàn)在我們的代碼是可以運(yùn)行的,,下面將代碼稍微整理下吧,。
善于使用Typedef
匆忙的聲明代碼塊的類型容易混亂,即使在這個(gè)簡單的例子中,,函數(shù)指正的語法還是有許多不足之處:
+ (void)iterateFromOneTo:(int)limit withBlock:(int (^)(int))block;
試想代碼塊要使用多個(gè)參數(shù),,并且有些參數(shù)是指針類型,這樣的話你幾乎需要完全重寫你的代碼,。為了提高可讀性和避免在.h和.m中出項(xiàng)重復(fù),,我們可以使用typedef修改Worker.h文件:
typedef int (^ComputationBlock)(int);
@interface Worker : NSObject {
}
+ (void)iterateFromOneTo:(int)limit withBlock:(ComputationBlock)block;
@end
typedef 是C語言的一個(gè)關(guān)鍵字,其作用可以理解為將一個(gè)繁瑣的名字起了一個(gè)昵稱,。在這種情況下,,我們定義一個(gè)代碼塊變量ComputationBlock,它有一 個(gè)int型參數(shù)和一個(gè)int型返回值,。然后,,我們定義iterateFromOneTo:withBlock:方法時(shí),可以直接使用 ComputationBlock作為代碼塊參數(shù),。同樣,,在Worker.m文件,我們可以通過使用ComputationBlock簡化代碼:
#import "Worker.h"
@implementation Worker
+ (void)iterateFromOneTo:(int)limit withBlock:(ComputationBlock)block {
for (int i = 1; i <= limit; i++) {
int result = block(i);
NSLog(@"iteration %d => %d", i, result);
}
}
@end
嗯,, 這樣就好多了,,代碼易于閱讀,,沒有在多個(gè)文件重復(fù)定義代碼塊類型。事實(shí)上,,你可以使用ComputationBlock在你程序的任何地方,,只要 import “Worker.h”,你會(huì)碰到類似的typedef在新的iOS4的API中,。例如,,ALAssetsLibrary類定義了下面的方法:
- (void)assetForURL:(NSURL *)assetURL
resultBlock:(ALAssetsLibraryAssetForURLResultBlock)resultBlock
failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock
這個(gè)方法調(diào)用兩個(gè)代碼塊,一個(gè)代碼塊時(shí)找到所需的資源時(shí)調(diào)用,,另一個(gè)時(shí)沒找到時(shí)調(diào)用,。它們 的 typedef如下:
typedef void (^ALAssetsLibraryAssetForURLResultBlock)(ALAsset *asset);
typedef void (^ALAssetsLibraryAccessFailureBlock)(NSError *error);
然后在你的程序中可以使用ALAssetsLibraryAssetForURLResultBlock和ALAssetsLibraryAccessFailureBlock去表示相應(yīng)的代碼塊變量。
我建議在寫一個(gè)使用代碼塊的公用方法時(shí)就用typedef,,這樣有助于你的代碼整潔,,并可以讓其他開發(fā)人員方便使用。
再來看一下閉包
你應(yīng)該還記得代碼塊是閉包,,我們簡要的講述一下在第一部分提及的閉包,。在第一部分閉包的例子并不實(shí)用,而且我說閉包在方法間傳遞時(shí)會(huì)變得特別有用?,F(xiàn)在我們已經(jīng)知道如何寫一個(gè)實(shí)用代碼塊的方法,,那么就讓我們分析下另一個(gè)閉包的例子:
int multiplier = 3;
[Worker iterateFromOneTo:5 withBlock:^(int number) {
return number * multiplier;
}];
我們使用之前寫的iterateFromOneTo:withBlock:方法,有一點(diǎn)不同的是沒有將要得到的倍數(shù)硬編碼到代碼塊中,,這個(gè)倍數(shù)被聲明在代碼塊之外,,為一個(gè)本地變量。該方法執(zhí)行的結(jié)果與之前一致,,將1到5之間的數(shù)乘3:
iteration 1 => 3
iteration 2 => 6
iteration 3 => 9
iteration 4 => 12
iteration 5 => 15
這個(gè)代碼的運(yùn)行是一個(gè)說明閉包強(qiáng)大的例子,。代碼打破了一般的作用域規(guī)則。實(shí)際上,,在iteratefromOneTo:withBlock:方法中調(diào)用multiplier變量,,可以把它看作是本地變量。
記 住,,代碼塊會(huì)捕捉周圍的狀態(tài),。當(dāng)一個(gè)代碼塊聲明時(shí)它會(huì)自動(dòng)的對(duì)其內(nèi)部用到的變量做一個(gè)只讀的快照。因?yàn)槲覀兊拇a塊使用了multiplier變量,,這個(gè) 變量的值被代碼塊保存了一份供之后使用,。也就是說,multiplier變量已經(jīng)成為了代碼塊狀態(tài)啊的一部分,。當(dāng)代碼塊被傳入到 iterateFromOneTo:withBlock:方法,快的狀態(tài)也傳了進(jìn)去,。
好吧,,如果我們想在代碼塊的內(nèi)部改變multiplier變量該怎么辦,?例如,代碼塊每次被調(diào)用時(shí)要讓multiplier變?yōu)樯弦淮斡?jì)算的結(jié)果,。你可能會(huì)試著在代碼塊里直接改變multiplier變量,,像這樣:
int multiplier = 3;
[Worker iterateFromOneTo:5 withBlock:^(int number) {
multiplier = number * multiplier;
return multiplier; // compile error!
}];
這樣的話是通不過編譯的,編譯器會(huì)報(bào)錯(cuò)“Assignment of read-only variable 'mutilplier'”,。這是因?yàn)榇a塊內(nèi)使用的是變量的副本,,它是堆棧里的一個(gè)常量。這些變量在代碼塊中是不可改變的,。
如果你想要修改一個(gè)在塊外面定義,,在塊內(nèi)使用的變量時(shí),你需要在變量聲明時(shí)增加新的前綴_block,,像這樣:
__block int multiplier = 3;
[Worker iterateFromOneTo:5 withBlock:^(int number) {
multiplier = number * multiplier;
return multiplier;
}];
NSLog(@"multiplier => %d", multiplier);
這樣代碼可以通過編譯,,運(yùn)行結(jié)果如下:
iteration 1 => 3
iteration 2 => 6
iteration 3 => 18
iteration 4 => 72
iteration 5 => 360
multiplier => 360
要 注意的是代碼塊運(yùn)行之后,multiplier變量的值已經(jīng)變?yōu)榱?60,。換句話說,,代碼塊內(nèi)部修改的不是變量的副本。聲明一個(gè)被_block修飾的變量 是將其引用傳入到了代碼塊內(nèi),。事實(shí)上,,被_block修飾的變量是被所有使用它的代碼塊共享的。這里要強(qiáng)調(diào)的一點(diǎn)是:_block不要隨便使用,。在將一些 東西移入內(nèi)存堆中會(huì)存在邊際成本,,除非你真的確定需要修改變量,否則不要用_block修飾符,。
編寫返回代碼塊的方法
有時(shí)我們會(huì)需要編寫一個(gè)返回代碼塊的方法,。讓我先看一個(gè)錯(cuò)誤的例子:
+ (ComputationBlock)raisedToPower:(int)y {
ComputationBlock block = ^(int x) {
return (int)pow(x, y);
};
return block; // Don't do this!
}
這種方法簡單的創(chuàng)建了一個(gè)計(jì)算y的x次冪的代碼塊然后返回它。它使用了我們之前通過typedef使用的ComputationBlock,。下面是我們對(duì)所返回代碼塊的期望效果:
ComputationBlock block = [Worker raisedToPower:2];
block(3); // 9
block(4); // 16
block(5); // 25
在上面的例子中,,我們使用的得到代碼塊,傳入相應(yīng)的參數(shù),,它應(yīng)該會(huì)返回傳入值的平方,。但是當(dāng)我們運(yùn)行它時(shí),會(huì)得到運(yùn)行時(shí)錯(cuò)誤”EXC_BAD_ACCESS”,。
怎么辦,?解決這個(gè)問題的關(guān)鍵是了解代碼塊是怎么分配內(nèi)存的。代碼塊的生命周期是在棧中開始的,,因?yàn)樵跅V蟹峙鋬?nèi)存是比較塊的,。是棧變量也就意味著它從棧中彈出后就會(huì)被銷毀。方法返回結(jié)果就會(huì)發(fā)生這樣的情況,。
回 顧我們的raisedToPower:方法,,可以看到在方法中創(chuàng)建了代碼塊并將它返回,。這樣創(chuàng)建代碼塊就是已明確代碼塊的生存周期了,當(dāng)我們返回代碼塊變 量后,,代碼塊其實(shí)在內(nèi)存中已經(jīng)被銷毀了,。解決辦法是在返回之前將代碼塊從棧中移到堆中。這聽起來很復(fù)雜,,但是實(shí)際很簡單,,只需要簡單的對(duì)代碼塊進(jìn)行 copy操作,代碼塊就會(huì)移到堆中,。下面是修改后的方法,,它可以滿足我們的預(yù)期:
+ (ComputationBlock)raisedToPower:(int)y {
ComputationBlock block = ^(int x) {
return (int)pow(x, y);
};
return [[block copy] autorelease];
}
注 意我們使用了copy后就必須跟一個(gè)autorelease從而平衡它的引用計(jì)數(shù)器,避免內(nèi)存泄露,。當(dāng)然我們也可以在使用代碼塊之后將其手動(dòng)釋放,,不過這 就不符合誰創(chuàng)建誰釋放的原則了。你不會(huì)經(jīng)常需要對(duì)代碼塊進(jìn)行copy操作,,但是如果是上面所講的情況你就需要了,,這點(diǎn)請(qǐng)留意。
將所學(xué)的整合在一起
那么,,讓我們來把所學(xué)的東西整合為一個(gè)更實(shí)際點(diǎn)的例子,。假設(shè)我們要設(shè)計(jì)一個(gè)簡單的播放電影的類,這個(gè)類的使用者希望電影播放完之后能夠接受一個(gè)用于展現(xiàn)應(yīng)用特定邏輯的回調(diào),。前面已經(jīng)證明代碼塊是處理回調(diào)很方便的方法,。
讓我們開始寫代碼吧,從一個(gè)使用這個(gè)類的開發(fā)人員的角度來寫:
MoviePlayer *player =
[[MoviePlayer alloc] initWithCallback:^(NSString *title) {
NSLog(@"Hope you enjoyed %@", title);
}];
[player playMovie:@"Inception"];
可 以看出我們需要MoviePlayer類,,他有兩個(gè)方法:initWithCallback:和playMovie:,,初始化的時(shí)候接受一個(gè)代碼塊,然后 將它保存起來,,在執(zhí)行playMovie:方法結(jié)束后再調(diào)用代碼塊,。這個(gè)代碼塊需要一個(gè)參數(shù)(電影的名字),返回void類型,。我們對(duì)回調(diào)的代碼塊類型使 用typedef,,使用property來保存代碼塊變量。記住,,代碼塊是對(duì)象,,你可以像實(shí)例變量或?qū)傩砸粯邮褂盟_@里我們將它當(dāng)作屬性使用,。下面是 MoviePlayer.h:
typedef void (^MoviePlayerCallbackBlock)(NSString *);
@interface MoviePlayer : NSObject {
}
@property (nonatomic, copy) MoviePlayerCallbackBlock callbackBlock;
- (id)initWithCallback:(MoviePlayerCallbackBlock)block;
- (void)playMovie:(NSString *)title;
@end
下面是MoviePlayer.m:
#import "MoviePlayer.h"
@implementation MoviePlayer
@synthesize callbackBlock;
- (id)initWithCallback:(MoviePlayerCallbackBlock)block {
if (self = [super init]) {
self.callbackBlock = block;
}
return self;
}
- (void)playMovie:(NSString *)title {
// play the movie
self.callbackBlock(title);
}
- (void)dealloc {
[callbackBlock release];
[super dealloc];
}
@end
在 initWithCallback:方法中將要使用的代碼塊聲明為callbackBlock屬性,。由于屬性被聲明為了copy方式,代碼塊會(huì)自動(dòng)進(jìn)行 copy操作,從而將其移到堆中,。當(dāng)playMovie:方法調(diào)用時(shí),,我們傳入電影的名字作為參數(shù)來調(diào)用代碼塊。
現(xiàn)在我們假設(shè)一個(gè)開發(fā)人員要在程序中使用我們的MoviePlayer類來管理一組你打算觀看的電影,。當(dāng)你看完一部電影之后,這部電影就會(huì)從組中移除,。下面是一個(gè)簡單的實(shí)現(xiàn),,使用了閉包:
NSMutableArray *movieQueue =
[NSMutableArray arrayWithObjects:@"Inception",
@"The Book of Eli",
@"Iron Man 2",
nil];
MoviePlayer *player =
[[MoviePlayer alloc] initWithCallback:^(NSString *title) {
[movieQueue removeObject:title];
}];
for (NSString *title in [NSArray arrayWithArray:movieQueue]) {
[player playMovie:title];
};
請(qǐng) 注意代碼塊使用了本地變量movieQueue,它會(huì)成為代碼塊狀態(tài)的一部分。當(dāng)代碼塊被調(diào)用,,就會(huì)從數(shù)組movieQueue中移除一個(gè)電影,,盡管此時(shí) 數(shù)組是在代碼塊作用域之外的。當(dāng)所有的電影播放完成之后,,movieQueue將會(huì)是一個(gè)空數(shù)組,。下面是一些需要提及的重要事情:
1、movieQueue變量是一個(gè)數(shù)組指針,,我們不能修改它的指向,。我們修改的是它指向的內(nèi)容,因此不需要使用_block修飾,。
2,、為了迭代movieQueue數(shù)組,我們需要?jiǎng)?chuàng)建一個(gè)它的copy,,否則如果我們直接使用movieQueue數(shù)組,,就會(huì)出現(xiàn)在迭代數(shù)組的同事還在移除它的元素,這會(huì)引起異常,。
3,、如果不使用代碼塊,我們可以聲明一個(gè)協(xié)議,,寫一個(gè)代理類,,并注冊(cè)這個(gè)代理作為回調(diào)。很明顯該例子使用內(nèi)聯(lián)代碼塊更方便,。
4,、在不改變MoviePlayer類的前提下可以給他增加新功能。比如另一個(gè)開發(fā)者可以在看完一部電影后將其分享到twitter或?qū)﹄娪斑M(jìn)行評(píng)價(jià)等,。
翻譯自 http:///blog/2010/7/28/ios4-blocks-1
|