Block是iOS在4.0之后新增的語法,在iOS SDK 4.0之后,block幾乎出現在所有新版的API之中,換句話說,如果不了解block這個概念就無法使用SDK 4.0版本以后的新功能,所以我們有必要去學習下block的使用了,英文比較好的童鞋可以自行參照官方文檔,本文中的示例采摘于官方文檔.
Block的定義
在這一小節(jié)我們先用一些簡單范例來引入block的概念。
1.1 聲明和創(chuàng)建Block:
我們使用「^」運算子來聲明一個block,而且在block的定義最后面要加上「;」來表示一個完整的述句,下面是一個block的范例:
int multiplier = 7;
int (^myBlock)(int) = ^(int num){
return num * multiplier;
};
例子中我們聲明了一個myBlock的代碼塊,用「^」符號來表示這是一個block。聲明告訴我們myBlock是一個入參和出參都為整型(int)的block。
值得注意的地方是block可以使用和本身定義范圍相同的變數,在上面的例子中multiplier 和myBlock 都是某一個函數內定義的兩個變量,都在某個函數兩個大括號「{」和「 }」中間的區(qū)塊,因此它們的有效范圍是相同的,所以在block中就可以直接使用 multiplier 這個變數,此外當把block定義成一個變量的時,我們可以直接像使用一般函數般的方式使用它:
int multiplier = 7 ;
int (^myBlock)( int ) = ^( int num){
return num * multiplier;
};
printf ( "%d" , myBlock( 3 ));
//結果會打印出21
1.2 直接使用Block
在很多情況下,我們并不需要將block聲明為變量,反之我們可以直接在需要使用block的地方直接用內嵌的方式將block的內容寫出來,在下面的例子中AFNet的一個函數,就是直接使用block做為它的參數:
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
}
1.3__block變量
一般來說,在block內只能讀取在同一個作用域的變量而且沒有辦法修改在block外定義的任何變量,如果我們想要這些變量能夠在block中被修改,就必須在前面掛上__block的修飾詞,以上面第一個例子中的 multiplier 來說,這個變數在 block 中是只讀的,所以 multiplier = 7 指定完后,在 block 中的 multiplier 就只能是 7 不能修改,若我們在 block 中修改 multiplier,在編譯時就會報錯,因此若要在 block 中修改 multiplier ,就必須在 multiplier 前面加上__block的修飾詞,請參考下面的范例:
__block int multiplier = 7;
int (^myBlock)(int) = ^(int num){
if (num > 5 ) {
multiplier = 7 ;
}
else {
multiplier = 10 ;
}
return num * multiplier;
};
printf ( "%d" , myBlock( 3 ));
//結果會打印出30
Block概要
Block 提供我們一種能夠將函數程式碼內嵌在一般述句中的方法,在其他語言中也有類似的概念稱做「closure」,也就是通常我們說的閉包的概念。
2.1 Block 的功能
Block 是一種具有匿名功能的內嵌函數,它的特性如下: 如一般的函數般能擁有帶有型態(tài)的參數。擁有回傳值。可以擷取被定義的詞法作用域(lexical scope)狀態(tài)。可以選擇性地修改詞法作用域的狀態(tài)。
注:詞法作用域(lexical scope)可以想像成是某個函數兩個大括號中間的區(qū)塊,這個區(qū)塊在程式執(zhí)行時,系統(tǒng)會將這個區(qū)塊放入堆疊記憶體中,在這個區(qū)塊中的宣告的變數就像是我們常聽到的區(qū)域變數,當我們說block可以擷取同一詞法作用域的狀態(tài)時可以想像block變數和其他區(qū)域變數是同一個層級的區(qū)域變數(位于同一層的堆疊里),而block的內容可以讀取到和他同一層級的其他區(qū)域變數。我們可以拷貝一個block,也可以將它丟到其他的執(zhí)行緒中使用,基本上雖然block在iOS程式開發(fā)中可以使用在C/C++開發(fā)的程式片段,也可以在Objective-C中使用,不過在系統(tǒng)的定義上,block永遠會被視為是一個Objective-C的物件。
2.2 Block 的使用時機
Block 一般是用來表示、簡化一小段的程式碼,它特別適合用來建立一些同步執(zhí)行的程式片段、封裝一些小型的工作或是用來做為某一個工作完成時的回傳呼叫(callback) 。在新的iOS API中block被大量用來取代傳統(tǒng)的delegate和callback,而新的API會大量使用block主要是基于以下兩個原因:
一、可以直接在程式碼中撰寫等會要接著執(zhí)行的程式,直接將程式碼變成函數的參數傳入函數中,這是新API最常使用block的地方。二、可以存取區(qū)域變數,在傳統(tǒng)的callback實作時,若想要存取區(qū)域變數得將變數封裝成結構才能使用,而block則是可以很方便地直接存取區(qū)域變數。
聲明和創(chuàng)建Block
3.1 聲明一個Block變量的參考
// 入參為空,出數也為空的block
void(^blockReturnVoidWithVoidArgument)(void);
// 出參為整型(int),兩個入參分別是整型(int)和字符類型(char)的block
int(^blockReturnIntWithIntAndCharArguments)(int,char);
//出參為空,含有10個block的數組,每個block都有一個類型為int的入參
void(^arrayOfTenBlocksReturnVoidWinIntArgument[10])(int);
3.2 創(chuàng)建一個Block
我們使用「^」聲明一個block,并在最后使用「;」來表示結束,下面的范例示范了一個block變量,然后再定義一個block把它指定給block變量:
/* 聲明block 變量*/
int (^oneBlock)(int);
/* 定義一個block 并指定給上面聲明的變量*/
oneBlock = ^(int anInt)
{
return anInt = - 1 ;
};
3.3 全局的Block
聲明一個全局的block,請參考以下范例:
int GlobalInt = 0;
int(^getGlobalInt)(void) = ^(void){
return GlobalInt;
}
Block和變量
4.1 變量的形態(tài)
我們可以在block中遇到平常在函數中會遇到的變量類型:
全域(global)變量或是靜態(tài)的區(qū)域變量(static local)。
全域的函數。
區(qū)域變量和由封閉領域(enclosing scope)傳入的參數。
除了上述之外block額外支援了另外兩種變量:在函數內可以使用**__block** 變量,這些變量在block中是可被修改的。匯入常數(const imports)。
此外,在方法的實作里,block可以使用Objective-C的實體變量(instance variable)。
下列的規(guī)則可以套用到在block中變量的使用:
可以存取全域變量和在同一領域(enclosing lexical scope)中的靜態(tài)變量。
可以存取傳入block的參數(使用方式和傳入函數的參數相同)。
在同一領域的區(qū)域變數在block中將視為常數(const)。
可以存取在同一領域中以__block 為修飾詞的變數。
在block中宣告的區(qū)域變數,使用方式和平常函數使用區(qū)域變數的方式相同。
下面的例子介紹了區(qū)域變數(上述第三點)的使用方式:
int x = 123;
void (^printXandY)(int) = ^(int y)
{
printf("%d##%d",x,y);
}
//printXAndY(456);將會輸出123##456
4.2block 型態(tài)變量
我們可以藉由將一個由外部匯入block的變量放上修飾詞
block來讓這個變數由只讀變成讀寫,不過有一個限制就是傳入的變量在記憶體中必須是一個占有固定長度記憶體的變數__block修飾詞無法使用于像是變動長度的陣列這類不定長度的變數,請參考下面的范例:
//加上__block 修飾詞,所以可以在block 中被修改。
__block int x = 123;
void (^printXandY)(int) = ^(int y){
x = x+y;
printf("%d %d",x,y);
}
printXAndY( 456 ); // 將會印出579 456
下面我們使用一個范例來介紹各類型的變數和block之間的互動:
extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
NSInteger localCounter = 42 ;
__block char localCharacter;
void(^aBlock)(void) = ^(void){
++ CounterGlobal; //可以存取
++ CounterStatic; //可以存取
CounterGlobal = localCounter; //localCounter在block 建立時就不可變了
localCharacter = 'a' ; //設定外面定義的localCharacter 變量
}
++localCounter; //不會影響的block 中的值
localCharacter = 'b';
aBlock(); //執(zhí)行block 的內容
//執(zhí)行完后,localCharachter 會變成'a'
}
4.3block 中的引用計數問題
我們在block中引用外部的變量,在一般的情況下它將會自動增加變量的引用計數,不過若以__block作為修飾,引用計數不受影響,因此我們需要注意兩點
1.若直接引用實例變量(instance variable),self的引用計數加1。
2.若通過變量存取實例變數的值,只是實例變量的引用計數加1。
以下范例說明上面兩種情況,假設instanceVariable是實體變量:
dispath_async(queue,^{
//因為直接存取實體變量instanceVariable ,所以self 的retain count會加1
doSomethingWithObject (instanceVariable);
});
id localVaribale = instanceVariable;
dispatch_async(queue,^{
//localVariable是存取值,所以這時只有l(wèi)ocalVariable 的retain count 加1
//self 的return count并不會增加。
doSomethingWithObject (localVaribale);
});
使用Block
5.1 直接使用
我們可以像使用一般函數的方式來使用它,請參考下面兩個范例:
int(^aBlock)(int) = ^(int aInt){
return aInt-1;
};
printf("10 minus 1 is %d", aBlock(10));
//結果會顯示:10 minus 1 is 9
float(^distanceTraveled)(float, float, float) = ^(float startingSpeed, float acceleration, float time){
float distance = (startingSpeed * time) + ( 0.5 * acceleration * time * time);
return distance;
};
float howFar = distanceTraveled( 0.0 , 9.8 , 1.0 );
//howFar的值為4.9
在一般常見的情況中,若是將block當做是參數傳入函數,我們通常會使用「內嵌」的方式來使用block。
5.2? 將Block當作函數的參數
我們可以像一般函數使用參數的方式,將block以函數參數的形式傳入函數中,在這種情況下,大多數我們使用block的方式將不會傾向聲明block而是直接以內嵌的方式來將block傳入,這也是目前SDK中主流的做法:
char *myCharacters[3]={"TomJohn","George","Charles Condomine"};
qsort_b(myCharacters, 3, sizeof(char *),^(const void *l,const void *r){
char *left = *(char **)l;
char *right = *( char **)r;
return strncmp (left, right, 1 );
}//這里是block 的終點。
);
//最后的結果為:{"Charles Condomine", "George", "TomJohn"}
作者:Cyy
鏈接:http://www.lxweimin.com/p/3bd42d61ee42
來源:簡書
著作權歸作者所有。商業(yè)轉載請聯繫作者獲得授權,非商業(yè)轉載請註明出處。