iOS中block技術小結

aswu5131 8年前發布 | 5K 次閱讀 iOS開發 移動開發

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

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

腳本語言:

function funA(callback){

alert(callback());

}

function funB(){

var str = "Hello World"; //函數funB的局部變量,函數funA的非局部變量

funA(

function(){

return str;

}

);

}

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

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

block原型及定義

block本質上是和其他變量類似。不同的是block存儲的數據是一個函數體。使用block時,你可以像調用其他標準函數一樣,傳入參數,并得到返回值。

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

按照調用函數的方式調用block對象變量就可以了:

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

使用typedef關鍵字

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

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

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

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

return c * d;

}

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

Block和變量

block被聲明后會捕捉創建點時的狀態。block可以訪問函數用到的標準類型的變量:

全局變量;

全局函數(這個是可以調用);

封閉范圍內的變量;

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

封閉范圍內的非靜態變量會被獲取為常量;

Objective-C對象;

block內部變量;

3.1 本地變量

本地變量就是與block在同一范圍內聲明的變量。

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 全局變量

全局變量或靜態變量在內存中的地址是固定的,block在讀取該變量值的時候是直接從其所在內存讀出,獲取到的是最新值,而不是在定義時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是復制其引用地址來實現訪問的。

3.4Objective-C對象

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

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

如果是通過引用來訪問一個實例變量,那么將強引用至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;第二種情況把實例變量變成了本地臨時變量,強引用將直接指向這個本地的臨時變量。有時第一種情況可能會造成循環引用(在后面的注意事項中會解釋),要避免強引用到self的話,用__weak把self重新引用一下,在block中使用weakSelf就行了,比如:

__weak UIViewController *weakSelf = self;

Block類型

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

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

int main()

{

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

return 0;

}

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

int main()

{

char a = 'A';

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

return 0;

}

_NSConcreteMallocBlock:保存在堆中的block,當引用計數為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。它既不在棧中,也不在堆中,可以理解為它在內存的text區。

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

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

使用block的注意事項

避免在block里用self造成循環引用

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

self.someBlock = ^(Type var) {

[self doSomething];

//或self.otherVar = XXX;

//或_otherVar = ...

};

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

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

開發過程中該選擇block還是delegate

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

總結

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

 

來自:http://www.jianshu.com/p/62ca84286507

 

 本文由用戶 aswu5131 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!