iOS中block技術小結
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