iOS中block技術小結

block是C語言級別的語法和運行時特性,應用到Objective-C中可以增強函數(shù)功能。在合適場景中靈活應用block技術,對實際開發(fā)大有裨益。

block是對C語言中函數(shù)的擴展,除了函數(shù)中的代碼,還包含變量的綁定。block有時也被稱為閉包(closure),閉包就是一個函數(shù),或者一個指向函數(shù)的指針,加上這個函數(shù)執(zhí)行的非局部變量。通俗一點,就是閉包允許一個函數(shù)訪問聲明該函數(shù)運行上下文中的變量,甚至可以訪問不同運行上文中的變量。

腳本語言:

function?funA(callback){

alert(callback());

}

function?funB(){

var?str?=?"Hello?World";?//函數(shù)funB的局部變量,函數(shù)funA的非局部變量

funA(

function(){

return?str;

}

);

}

通過上面的代碼我們可以看出,按常規(guī)思維來說,變量str是函數(shù)funB的局部變量,作用域只在函數(shù)funB中,函數(shù)funA是無法訪問到str的。但是上述代碼示例中函數(shù)funA中的callback可以訪問到str,就是因為閉包性。

block實際上就是Objective-C語言對于閉包的實現(xiàn)。block配合dispatch_queue,可以方便地實現(xiàn)簡單的多線程編程和異步編程。

block原型及定義

block本質(zhì)上是和其他變量類似。不同的是block存儲的數(shù)據(jù)是一個函數(shù)體。使用block時,你可以像調(diào)用其他標準函數(shù)一樣,傳入?yún)?shù),并得到返回值。

脫字符(^)是block的語法標記,按照我們熟悉的參數(shù)語法規(guī)約所定義的返回值以及block的主體(也就是可以執(zhí)行的代碼)。下圖講解了如何把block變量賦值給一個變量的語法:

按照調(diào)用函數(shù)的方式調(diào)用block對象變量就可以了:

int?result?=?myBlock(4);?//result是28

使用typedef關鍵字

由于block數(shù)據(jù)類型的語法會降低整個代碼的閱讀性,所以常使用typedef來定義block類型。

typedef?double?(^Multiply2BlockRef)(double?c,?double?d);

這行語句定義了一個名為Multiply2BlockRef的block變量,它包含兩個double類型參數(shù)并返回一個double類型數(shù)值。有了typedef定義,就可以像下面這樣使用這個變量:

Multiply2BlockRef?multiply2?=?^(double?c,?double?d)?{

return?c?*?d;

}

printf(“%f”,?multiply2(4,?5));

Block和變量

block被聲明后會捕捉創(chuàng)建點時的狀態(tài)。block可以訪問函數(shù)用到的標準類型的變量:

全局變量;

全局函數(shù)(這個是可以調(diào)用);

封閉范圍內(nèi)的變量;

與block聲明時同級別的__block變量(這是可以修改的);

封閉范圍內(nèi)的非靜態(tài)變量會被獲取為常量;

Objective-C對象;

block內(nèi)部變量;

3.1 本地變量

本地變量就是與block在同一范圍內(nèi)聲明的變量。

typedef?double?(^Multiply2BlockRef)(double?c,?double?d);

double?a?=?10,?b?=?10;

Multiply2BlockRef?multiply?=?^(void)?{?return?a?*?b;}

a?=?20;

b?=?20;

NSLog(@“%f”,?multiply());

這個NSLog會輸出什么值?400?不是,為什么?

因為對于本地變量,block定義時copy變量的值,在block中作為常量使用,所以即使變量的值在block外改變,也不影響他在block中的值。NSLog只會輸出100。

3.2 全局變量

全局變量或靜態(tài)變量在內(nèi)存中的地址是固定的,block在讀取該變量值的時候是直接從其所在內(nèi)存讀出,獲取到的是最新值,而不是在定義時copy的常量。

3.3 Block變量

本地變量會被block作為常量獲取到,如果想要修改它們的值,必須將它們聲明為可修改的,否則會編譯出錯。

double?c?=?3;

Multiply2BlockRef?multiply2?=?^(double?a,?double?b){?c?=?a?*?b;?}?//?編譯會報錯

如果想要在block代碼里修改本地變量,需要將變量標記為__block。基于之前的代碼,給變量c添加__block關鍵字,如下:

__block?double?c?=?3;

對于用__block修飾的外部變量引用,block是復制其引用地址來實現(xiàn)訪問的。

3.4Objective-C對象

一般來說我們總會在設置block之后,在合適的時間回調(diào)block,而不希望回調(diào)block的時候block已經(jīng)被釋放了,所以我們需要對block進行copy,copy倒堆中,以便后用。

當一個block被Copy的時候,如果你在block里進行了一些調(diào)用,那么將會有一個強引用指向這些調(diào)用方法的調(diào)用者,有兩個規(guī)則:

如果是通過引用來訪問一個實例變量,那么將強引用至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

(not?to?self)

*/

doSomethingWithObject(localVariable);

});

上面第一種情況相當于通過self.xxx來訪問實例變量,所以強引用指向了self;第二種情況把實例變量變成了本地臨時變量,強引用將直接指向這個本地的臨時變量。有時第一種情況可能會造成循環(huán)引用(在后面的注意事項中會解釋),要避免強引用到self的話,用__weak把self重新引用一下,在block中使用weakSelf就行了,比如:

__weak?UIViewController?*weakSelf?=?self;

Block類型

block有幾種不同的類型,這里列出常見的三種類型:

_NSConcreteGlobalBlock:全局的靜態(tài)block,不會訪問任何外部變量,不會涉及到任何拷貝,比如一個空的block。例如:

int main()

{

^{ printf("Hello, World!\n"); } ();

return 0;

}

_NSConcreteStackBlock:保存在棧中的block,當函數(shù)返回時被銷毀。例如:

int main()

{

char a = 'A';

^{ printf("%c\n",a); } ();

return 0;

}

_NSConcreteMallocBlock:保存在堆中的block,當引用計數(shù)為0時被銷毀。該類型的block都是由_NSConcreteStackBlock類型的block從棧中復制到堆中形成的。例如下面代碼中,在exampleB_addBlockToArray方法中的block還是_NSConcreteStackBlock類型的,在exampleB方法中就被復制到了堆中,成為_NSConcreteMallocBlock類型的block:

void exampleB_addBlockToArray(NSMutableArray *array) {

char b = 'B';

[array addObject:^{

printf("%c\n", b);

}];

}

void exampleB() {

NSMutableArray *array = [NSMutableArray array];

exampleB_addBlockToArray(array);

void (^block)() = [array objectAtIndex:0];

block();

}

小結

_NSConcreteGlobalBlock類型的block要么是空block,要么是不訪問任何外部變量的block。它既不在棧中,也不在堆中,可以理解為它在內(nèi)存的text區(qū)。

_NSConcreteStackBlock類型的block有閉包行為,也就是有訪問外部變量,并且該block有且只有一次執(zhí)行,因為棧中的空間是可重復使用的,所以當棧中的block執(zhí)行一次之后就被清除出棧了,所以無法多次使用。

_NSConcreteMallocBlock類型的block有閉包行為,并且該block需要被多次執(zhí)行。當需要多次執(zhí)行時,就會把該block從棧中復制到堆中,供以多次執(zhí)行。

使用block的注意事項

避免在block里用self造成循環(huán)引用

block在copy時都會對block內(nèi)部用到的對象進行強引用(ARC)或者retainCount增加1(MRC)。在ARC與MRC環(huán)境下對block使用不當都會引起循環(huán)引用問題。一般表現(xiàn)為,某個類將block作為自己的屬性變量,然后該類在block的方法體里面又使用了該類本身,簡單說就是block的這種循環(huán)引用會被編譯器捕捉到并及時提醒。

self.someBlock?=?^(Type?var)?{

[self?doSomething];

//或self.otherVar?=?XXX;

//或_otherVar?=?...

};

即使在block代碼中沒有顯式地出現(xiàn)"self",也會出現(xiàn)循環(huán)引用!只要在block里用到了self所擁有的東西!

對于這種情況,可以通過添加__weak聲明(ARC)或者__block聲明(MRC)去禁止block對self進行強引用或者強制增加引用計數(shù)。

開發(fā)過程中該選擇block還是delegate

如果對象有超過一個以上不同的事件源,使用delegation;一般的delegate方法會有返回值;delegate的回調(diào)更多的面向過程,而block則是面向結果的。如果需要得到一條多步進程的通知,應該使用delegation。而只是希望得到請求的信息(或者獲取信息時的錯誤提示),應該使用block。

總結

可見靈活安全地使用block,必定會使編碼工作事半功倍。block經(jīng)常被用作回調(diào)函數(shù),取代傳統(tǒng)的回調(diào)方式,可以使得編碼時更順暢,不用中途換到另一個地方寫一個回調(diào)函數(shù)。采用block,可以在調(diào)用函數(shù)時直接寫后續(xù)處理代碼,將其作為參數(shù)傳遞過去,供其任務執(zhí)行結束時回調(diào)。block通常適用于以下場景:任務完成時回調(diào);處理消息監(jiān)聽回調(diào)處理;錯誤回調(diào)處理;枚舉回調(diào);視圖動畫、變換;排序等。

本文作者:張生輝(點融黑幫),來自點融北京技術團隊,曾在索貝做過3年C++開發(fā),目前主要做iOS開發(fā),對前端技術及新興技術比較感興趣。

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

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