一读处、 block語法格式,如下
//return type (^BlockName)(list of arguments) = ^return type (list of arguments){do something;};
根據(jù)上面的英文應(yīng)該就能理解各部分是什么了,接下來是一個簡單的例子丹锹,可以幫助理解扒接,這里引用了 ?Sindri的小巢(簡書作者)的文章
原文鏈接:http://www.reibang.com/p/29d70274374b
int (^sumOfNumbers) (int a, int b) = ^int(int a, int b) {
return a + b;
};
//Block的調(diào)用
int result = sumOfNumbers(100, 200); //無參時,也要帶上小括號
NSLog(@"result = %d", result);
1.等號左側(cè)聲明一個名為sumOfNumbers的代碼塊精钮,名稱前用^符號表示后面的字符串是block的名稱;
2.最左側(cè)的int表示這個block的返回值乞娄;
3.括號中間表示這個block的參數(shù)列表瞬逊,這里接收兩個int類型的參數(shù)显歧。
4. 而在等號右側(cè)表示這個block的實現(xiàn),其中返回值類型是可以省略的确镊,編譯器會根據(jù)上下文自動補充返回值類型士骤。
5.使用^符號銜接著一個參數(shù)列表,使用括號包起來蕾域,告訴編譯器這是一個block拷肌,然后使用大括號將block的代碼封裝起來。
二旨巷、 block的直接使用(匿名Block對象)
NSArray *array = @[@"3", @"1", @"2"];
array = [array sortedArrayUsingComparator:^NSComparisonResult(id ?_Nonnull obj1, id ?_Nonnull obj2) {
return[obj1 compare: obj2];
}];
NSLog(@"array = %@", array);
其實理解起來也很簡單巨缘,匿名的Block對象/是可以傳遞給/方法的Block對象/的,而不需要先賦值給變量契沫。
讓我們先看看匿名的整數(shù)。有三種方法可以將整數(shù)傳遞給方法:
方法1: 聲明昔汉、賦值和使用完全分開
int i;
i = 5;
NSNumber *num = [NSNumber numberWithInt:i];
方法2: 在一行中聲明賦值使用
int i = 5;
NSNumber *num = [NSNumber numberWithInt:i];
方法3:跳過變量聲明步驟
NSNumber *num = [NSNumber numberWithInt:5];
如果采用第三種方法懈万,就是匿名地傳遞一個整數(shù)。因為它沒有名字靶病,所以說它是匿名的会通。
而將Block對象傳遞給方法的辦法和傳遞整數(shù)相同。分別用三行代碼來聲明Block對象娄周,然后賦值涕侈,最后使用。但是匿名傳遞Block對象更加常用煤辨。
三裳涛、類型重定義(block重命名)
Block對象的語法可能會比較復(fù)雜。通過使用第11章介紹過的typedef關(guān)鍵字众辨,可以將某個Block對象類型定義為一個新類型端三,以方便使用。需要注意的是鹃彻,不能在方法的實現(xiàn)代碼中使用typedef郊闯。也就是說,應(yīng)該在實現(xiàn)文件的頂部蛛株,或者頭文件內(nèi)使用typedef团赁。在main.m中,添加以下代碼:
#import
typedef int (^SumBlock)(int a, int b);
int main (int argc, const char * argv[])
{
這段代碼中的typedef語句看上去與Block變量聲明很像谨履,但是欢摄,這里定義的是一個新的類型,而不是變量笋粟。跟在^字符后面的是類型名稱剧浸。創(chuàng)建這個新類型后锹引,就能簡化相應(yīng)Block對象的聲明。
現(xiàn)在使用新的類型聲明SumBlock:
注意唆香,這里的Block類型只是聲明了Block對象的實參和返回類型嫌变,并沒有實現(xiàn)真正的Block對象。
SumBlock sumBlock = ^(int a, int b){
return a + b;
};
int result = sumBlock(100, 200);
NSLog(@"result = %d", result);
四躬它、外部變量 ——?block對棧區(qū)變量做只讀拷貝操作,使用的是對變量的copy,而不是變量本身
Block對象通常會(在其代碼中)使用外部創(chuàng)建的其他變量(基本類型的變量腾啥,或者是指向其他對象的指針)。這些外部創(chuàng)建的變量叫做外部變量(external variables)冯吓。當(dāng)執(zhí)行Block對象時倘待,為了確保其下的外部變量能夠始終存在,相應(yīng)的Block對象會捕獲(captured)這些變量组贺。對基本類型的變量凸舵,捕獲意味著程序會拷貝變量的值,并用Block對象內(nèi)的局部變量保存失尖。對指針類型的變量啊奄,Block對象會使用強引用。這意味著凡是Block對象用到的對象掀潮,都會被保留菇夸。所以在相應(yīng)的Block對象被釋放前,這些對象一定不會被釋放(這也是Block對象和函數(shù)之間的差別仪吧,函數(shù)無法做到這點)庄新。
使用外部變量
int a = 200, b = 100;
int (^minusBlock) (void) = ^(void){return a - b;};
NSLog(@"minus1 = %d", minusBlock());
//結(jié)果為100
a = 500, b = 200;
NSLog(@"minus2 = %d", minusBlock());
//結(jié)果還是100
只需要在定義外部變量的時候,使用 __block 或者 static 修飾薯鼠。
__block int a = 200, b = 100;//或者 static
int (^minusBlock) (void) = ^(void){return a - b;};
NSLog(@"minus1 = %d", minusBlock());
//結(jié)果為100
a = 500, b = 200;
NSLog(@"minus2 = %d", minusBlock());
//結(jié)果是300
修改外部變量
在Block對象中择诈,被捕獲的變量是常數(shù),程序無法修改變量所保存的值出皇。如果需要在Block對象內(nèi)修改某個外部變量吭从,則可以在聲明相應(yīng)的外部變量時,在前面加上__block關(guān)鍵字恶迈,否則涩金,如果在block內(nèi)部改變外部變量的值程序就會報錯
這樣寫是沒問題的
void(^oneBlock)(void) = ^(void){
int n = 1;
n = 2;
NSLog(@"n = %d", n);
};
oneBlock();//結(jié)果是2
下面的寫法會報錯
int n = 1;
//定義Block
void(^oneBlock)(void) = ^(void){
n = 2;//報錯!!!!!!
NSLog(@"n = %d", n);
};
oneBlock();
同樣可以使用__block修飾外部變量來解決這個問題(使用static修飾或定義成實例變量的方法解決)
__block int n = 1;
//定義Block
void(^oneBlock)(void) = ^(void){
n = 2;
NSLog(@"n = %d", n);
};
oneBlock();//結(jié)果是2
在block對象中使用self
如果需要寫一個使用self的Block對象,就必須要多做幾步工作來避免循環(huán)引用問題暇仲。下面舉個簡單的例子
self.nameLabel.text = @"xiaoming";
void(^strBlock)(void) = ^(void){
NSString*name = self.nameLabel.text;
NSLog(@"%@", name);
};
strBlock();
Block中使用了self步做,這個Block對象會捕獲self,Block必須等到所引用的對象全部釋放后才會釋放奈附,然而全度,self又要到程序運行結(jié)束才釋放,這樣Block就不可能得到釋放斥滤,就陷入強引用循環(huán)了将鸵。
為了打破這個強引用循環(huán)勉盅,可以先在Block對象外聲明一個__block指針(ARC下使用__weak),然后將這個指針指向Block對象使用的self顶掉,最后在Block對象中使用這個新的指針:
__weak RootViewController *weakSelf = self; // 一個弱引用指針
void(^strBlock)(void) = ^(void){
NSString*name =weakSelf.nameLabel.text;
NSLog(@"%@", name);
};
如果想要了解的更深入草娜,可以繼續(xù)看完下面的內(nèi)容,舉例有所變化痒筒,但應(yīng)該可以理解宰闰。
=======================================
現(xiàn)在這個Block對象對BNREMployee實例是弱引用,強引用循環(huán)打破了簿透。
然而移袍,由于是弱引用,所以self指向的對象在Block執(zhí)行的時候可能會被釋放老充。
為了避免這種情況的發(fā)生葡盗,可以在Block對象中創(chuàng)建一個對self的局部強引用:
__weak BNREmployee *weakSelf = self; // 弱引用
myBlock = ^{
BNREmployee *innerSelf = weakSelf; // 局部強引用
NSLog(@"Employee: %@", innerSelf);
};
通過創(chuàng)建innerSelf強引用,就可以在Block和BNREmployee實例中再次創(chuàng)建一個強引用循環(huán)啡浊。但是觅够,由于innerSelf引用是針對Block內(nèi)部的,所以只有在Block執(zhí)行的時候它才會執(zhí)行虫啥,而Block結(jié)束之后就會自動消失蔚约。
每次寫B(tài)lock對象的時候都引用self會是一個很好的練習(xí)奄妨。
在Block對象中無意使用self涂籽,而是使用了實例變量的情況
如果直接在Block對象中使用實例變量,那么block會捕獲self砸抛,而不會捕獲實例變量评雌。這是實例變量的一個鮮為人知的特點。例如直焙,以下這段代碼直接存取一個實例變量:
__weak BNREmployee *weakSelf = self;
myBlock = ^{
BNREmployee *innerSelf = weakSelf; // 局部強引用
NSLog(@"Employee: %@", innerSelf);
NSLog(@"Employee ID: %d", _employeeID);
};
編譯器是這么解讀這段代碼的:
__weak BNREmployee *weakSelf = self;
myBlock = ^{
BNREmployee *innerSelf = weakSelf; // 局部強引用
NSLog(@"Employee: %@", innerSelf);
NSLog(@"Employee ID: %d", self->_employeeID);
};
->語法看上去是不是很熟悉景东?這個語法實際是用來后去堆上的成員結(jié)構(gòu)的。從最底層來說奔誓,對象實際就是結(jié)構(gòu)斤吐。
由于編譯器將_employeeID看成是self->_employeeID,self就被Block對象無意地捕獲了厨喂。這樣又會造成之前使用weakSelf和innerSelf避免的強引用循環(huán)和措。
怎樣解決呢?不要直接存取實例變量蜕煌。使用存取方法派阱!
__weak BNREmployee *weakSelf = self;
myBlock = ^{
BNREmployee *innerSelf = weakSelf; // 局部強引用
NSLog(@"Employee: %@", innerSelf);
NSLog(@"Employee ID: %d", innerSelf.employeeID);
};
現(xiàn)在沒有直接地使用self了,就不會造成無意識地強引用循環(huán)斜纪。
在這種情況下贫母,重要的是要理解編譯器是如何思考的文兑,這樣才能避免隱藏的強引用循環(huán)。