什么是塊對象
C編譯器和GCD
塊對象(block Object)不是Objective-C而是C語言的功能實現(xiàn)危尿。在其他編程語言中,它與閉包(closure)功能相同够傍。
塊對象的定義
塊對象的參數(shù)列和主體部分的書寫方法與普通函數(shù)相同菇晃。主體中如果有return,就可以定義返回值塊乓搬。格式如下:
^(參數(shù)列){主體}
從^開始到參數(shù)列,主體最后的大括號代虾,這一段記述稱為塊對象的塊句法(block literal)进肯。實際上,塊句法并不被用于在內(nèi)存中分配的塊對象棉磨,它只是編寫代碼時的一種表達用語江掩。
塊對象本身常用于帶入到變量后評估,或被作為函數(shù)或方法的參數(shù)傳入等。此時环形,變量或參數(shù)的類型聲明和函數(shù)指針使用相同的書寫方法策泣。只是函數(shù)指針聲明中使用“*”,而塊對象使用“^”抬吟。
塊對象和類型聲明
同函數(shù)指針一樣萨咕,為了簡化書寫,可以使用typedef簡化聲明火本。如:
typedef int (^myBlockType) (int);
使用該類型后任洞,聲明函數(shù)func就可以使用下面的方式:
void func(myBlockType block);
如果塊對象沒有參數(shù)发侵,參數(shù)列則可以設(shè)置為(void),此外也可以將參數(shù)列連同括號全部省略妆偏,或者只保留括號刃鳄。
此外,剋通過_ _BLOCKS_ _宏來檢查當(dāng)前系統(tǒng)環(huán)境下是否可使用塊對象钱骂。根據(jù)編譯的不同條件叔锐,即可區(qū)別可以使用塊對象時的情況和不可以使用塊對象的情況。
塊對象中的變量行為
塊對象只在塊句法中保存自動變量的值见秽。
塊對象就是把可以執(zhí)行的代碼和代碼中可訪問的變量“封裝”起來愉烙,使得之后可以做進一步處理的包。而閉包這個稱呼本身就是把變量等執(zhí)行環(huán)境封裝起來的意思解取。我們把閉包引用步责,讀取外部變量稱為捕獲(capture)。
總結(jié):
塊句法主題中禀苦,除塊句法內(nèi)部的局部變量和形參外蔓肯,還包括當(dāng)前位置處可以訪問的變量。這些變量中有外部變量振乏,還有包含塊句法的代碼塊內(nèi)可以訪問的局部變量蔗包。
從塊對象內(nèi)部可以直接訪問外部變量和靜態(tài)變量(static變量),也可以直接改變變量的值慧邮。
在包含塊句法的代碼塊內(nèi)可訪問的局部變量中调限,書寫句法塊時自動變量(棧內(nèi)變量)的值會被保存起來,然后再被訪問误澳。
所以耻矮,即使自定變量最初的值發(fā)生了變化,塊對象在使用時也不會知道忆谓。
自動變量的值可以被讀取但是不能被改變淘钟。
自動變量為數(shù)組時,會發(fā)生編譯錯誤。
簡言之米母,在塊對象中雖然可以使用可訪問的變量勾扭,但自動變量的話就只能讀取復(fù)制值。換言之铁瞒,自動變量在運行時就相當(dāng)于const修飾的變量妙色。
排序函數(shù)和塊對象
塊對象可以實現(xiàn)和函數(shù)指針相同的功能。使用函數(shù)指針時慧耍,需要寫不同的函數(shù)來應(yīng)對各種不同的功能身辨,此外,為了給函數(shù)傳遞需要的附加信息芍碧,往往還要使用多余的參數(shù)和外部變量煌珊,而這些都違背了編寫代碼要盡可能獨立易懂的原則。
通過靈活使用塊對象泌豆,我們就可以將這樣的函數(shù)或方法實現(xiàn)為易讀定庵,靈活的書寫方式。
塊對象的構(gòu)成
塊對象的實例和生命周期
在編譯塊句法時踪危,會生成存放必要信息的內(nèi)存地址(實為結(jié)構(gòu)體)和函數(shù)蔬浙。變量中帶入的以及向函數(shù)傳入的實參,實際上就是這片內(nèi)存區(qū)域的指針贞远。
在函數(shù)外部的塊句法被編譯后畴博,塊對象的內(nèi)存區(qū)域就同外部變量一樣被配置在了靜態(tài)數(shù)據(jù)區(qū)中。
執(zhí)行包含塊句法的函數(shù)時蓝仲,和自動變量相同俱病,塊對象的內(nèi)存區(qū)域會在棧上得到分配。因此這些塊對象的生命周期也和自動變量相同袱结,只在函數(shù)執(zhí)行期間存在庶艾。
#include
voidpr(int(^block)(void)) {
printf("%d\n", block());
}
int(^g)(void) = ^{return100; };
voidfunc1(intn) {
int(^b1)(void) = ^{returnn; };
pr(b1);
g = b1;
// assign the local block
}
voidfunc2(intn) {
inta =10;
int(^b2)(void) = ^{
returnn * a; };
pr(b2);
}
intmain(void)
{
pr(g);
func1(5);
func2(5);
pr(g); ?//
會發(fā)生于運行時錯誤
return0;
}
塊對象將要保存的自動變量的信息復(fù)制到了內(nèi)存區(qū)域。該內(nèi)存區(qū)域也包含了評估塊對象時所執(zhí)行的函數(shù)指針等的信息擎勘。
即使反復(fù)執(zhí)行塊句法處的代碼咱揍,也不會每次都為塊對象動態(tài)分配一片新的內(nèi)存區(qū)域。但是棚饵,被復(fù)制到內(nèi)存區(qū)域中的自動變量的值每次都會更新煤裙。另一方面,含塊句法的函數(shù)在遞歸調(diào)用時噪漾,同自動變量相同硼砰,塊對象就會在棧上保存多個內(nèi)存區(qū)域。
總結(jié):
塊句法寫在函數(shù)外面時欣硼,只在靜態(tài)數(shù)據(jù)區(qū)分配一片內(nèi)存區(qū)域給塊對象题翰。這片區(qū)域在程序執(zhí)行期會一直存在。
塊句法寫在函數(shù)內(nèi)時,和自動變量一樣豹障,塊對象的內(nèi)存區(qū)域會在執(zhí)行包含塊對象的函數(shù)時被保存在棧上冯事。該區(qū)域的生命周期就是在函數(shù)運行期間。
此外血公,在現(xiàn)在的實現(xiàn)中昵仅,當(dāng)函數(shù)內(nèi)的塊句法不包含自動變量時,就沒必要復(fù)制值累魔,所以塊對象會被設(shè)置在靜態(tài)數(shù)據(jù)區(qū)摔笤。但因為實現(xiàn)方法可能改變,應(yīng)該避免編寫具有這種依賴關(guān)系的程序垦写。
應(yīng)該避免的編碼模式
上面注釋處代碼之所以會發(fā)生運行時錯誤吕世,是因為棧上生成的塊對象在生命周期外是不能被使用的。
證明塊對象只有一個實體的實例:
voidfunc1(void) {
int i;
int (^blocks1[10])(void);
for(i =0; i <10; i++)
blocks1[i] = ^{returni; };
for(i =0; i <10; i++)
pr(blocks1[i]);
}
如果想為數(shù)組的各個元素代入不同的塊對象梯投,就必須要進行下一節(jié)中所說的復(fù)制命辖。但是,使用ARC時操作是不同的晚伙。
塊對象的復(fù)制
有一個函數(shù)可以復(fù)制塊對象到新的堆區(qū)域。通過使用該功能俭茧,即使是函數(shù)內(nèi)的塊對象也能獨立于棧被持續(xù)使用咆疗。此外,還有一個函數(shù)可以釋放不需要的塊對象母债。
Block_copy(block)
參數(shù)為棧上的塊對象時午磁,返回堆上復(fù)制的塊對象。否則(參數(shù)為靜態(tài)數(shù)據(jù)區(qū)或為堆上的塊對象)則不進行復(fù)制而直接將參數(shù)返回毡们,但會增加參數(shù)的塊對象的引用計數(shù)迅皇。
Block_release(block)
減少參數(shù)塊對象的引用計數(shù),減到0時釋放塊對象的內(nèi)存區(qū)域衙熔。
使用這些函數(shù)時登颓,源文件中需要添加頭文件Block.h
如前所述,堆上分配的塊對象使用引用計數(shù)來管理红氯。即使在使用垃圾回收的情況下框咙,也必須成對調(diào)用Block_copy和Block_release。
指定特殊變量_ _block
通過_ _block修飾的變量有如下功能:
1.函數(shù)內(nèi)塊句法引用的_ _block變量是塊對象可以讀取的變量痢甘。同一個變量作用域內(nèi)有多個塊對象訪問時喇嘱,他們之間可以共享_ _block變量的值。
2._ _block變量不是靜態(tài)變量塞栅,它在塊句法每次執(zhí)行塊句法時獲取變量的內(nèi)存區(qū)域者铜。也就是說,同一個塊(變量作用域)內(nèi)的塊對象以及它們之間共享的_ _block變量是在執(zhí)行時動態(tài)生成的。
3.訪問_ _block變量的塊對象在被復(fù)制后作烟,新生成的塊對象也能共享_ _block變量的值愉粤。
4.多個塊對象訪問一個_ _block變量時,只要有一個塊對象存在著俗壹,_ _block變量就會隨之存在科汗。如果訪問_ _block變量的塊對象都不在了,_ _block也會隨之消失绷雏。
因為可能會涉及到實現(xiàn)头滔,這里省略了對_ _block變量行為的說明。但有一點涎显,隨著塊對象的復(fù)制坤检,_ _block的內(nèi)存位置會發(fā)生變化。而且不要寫使用指針來引用_ _block變量的代碼期吓。
Objective和塊對象
方法定義和塊對象
塊對象作為方法參數(shù)傳遞時參數(shù)類型的指定方法:
現(xiàn)在假如有一個塊對象:BOOL(^block) (int,int) = ^(intindex,intlength){...;};
使用該塊對象作為參數(shù)的方法setBlock:聲明如下早歇。“(BOOL(^)(int,int)) ”為參數(shù)類型
- (void)setBlock: (BOOL(^)(int,int)) block;
類型部分中也可以寫上形式參數(shù)名讨勤。
在OC中使用塊對象時箭跳,在塊句法內(nèi)也可以寫消息等OC的語法元素。
作為Objective-C對象的塊對象
OC程序在編譯運行時潭千,塊對象會成為OC的對象來執(zhí)行操作谱姓。
有一點需要注意,retainCount方法返回的引用計數(shù)結(jié)果是不正確的刨晴。
ARC和塊對象
使用ARC和不使用ARC時屉来,塊對象的操作是有區(qū)別的。
在ARC中狈癞,需要保存塊對象時茄靠,編譯器會自動插入copy操作。具體的說蝶桶,就是在被帶入強訪問變量以及被作為return的返回值返回的時候慨绳。這些情況下,程序不需要顯式地執(zhí)行塊對象的副本真竖。
但是儡蔓,作為方法參數(shù)傳入的塊對象是不會自動執(zhí)行copy的。而且疼邀,當(dāng)塊對象聲明為屬性值時喂江,屬性選項一般會指定(copy)。
不要使用Block_copy和Block_release旁振。因為已經(jīng)定義了(void *)型參數(shù)指針获询,所以ARC不能推測所有者涨岁。
_ _block變量的行為不同。不使用ARC時吉嚣,_ _block變量只帶入值梢薪,示情況可能會懸空指針。ARC中因為有_ _strong修飾符修飾_ _block變量尝哆,使其作為強訪問變量來使用秉撇,因此就不會成為懸空指針。
對象內(nèi)變量的行為
介紹塊句法內(nèi)使用塊對象時的行為秋泄,特別是引用計數(shù)琐馆。
void(^cp)(void);
- (void)someMethod {
idobj = ...;
intn =10;
void(^block)(void) = ^{ [obj calc:n]; };
...
cp = [block copy];
}
如下圖a所示,塊對象在棧上生成恒序,自動變量obj和n可以在塊內(nèi)使用瘦麸。obj引用任意實例對象時,塊對象內(nèi)使用的變量obj也會訪問同一個對象歧胁。這時滋饲,變量的引用計數(shù)不會發(fā)生改變。
接下來塊對象自身被復(fù)制喊巍,并在堆區(qū)域中生成了新的塊對象屠缭。(圖b)。這里崭参,實例對象的引用計數(shù)加1呵曹,由于方法執(zhí)行結(jié)束后自動變量obj也會消失,因此引用計數(shù)加1就使得塊對象成為了所有者阵翎。實例對象不是被復(fù)制逢并,而是被共享之剧,不只從塊對象郭卫,從哪都可以發(fā)送消息。
需要注意的是在某個類方法內(nèi)的塊句法被書寫了同一類的實例變量這種情況背稼。如下面這個例子贰军,假設(shè)ivar為包含方法someMethod的類實例變量。
void(^cp)(void);
- (void)someMethod {
intn =10;
void(^block)(void) = ^{ [ivar calc:n]; };
...
cp = [block copy];
}
這種情況下蟹肘,當(dāng)塊對象被復(fù)制時词疼,self的引用計數(shù)將加1,而非ivar帘腹。如下圖所示贰盗,方法的引用參數(shù)self在堆上分配。在上例中阳欲,self好像沒有出現(xiàn)在塊句法中舵盈,我們可以按下面方式理解:
^{ [self->ivar calc:n]; };
塊句法內(nèi)的實例變量為整數(shù)或者實數(shù)時也是一樣的陋率,self的引用計數(shù)也會增加。也就是說秽晚,當(dāng)與self對等的對象不存在時瓦糟,所有的實例變量都將不能訪問。
塊對象和對象的關(guān)系總結(jié)如下:赴蝇?
方法定義內(nèi)的塊句法中存在實例變量時菩浙,可以直接訪問實例變量,也可以改變其值句伶。
方法定義內(nèi)的塊句法中存在實例變量時劲蜻,如果在棧上生成的塊對象的副本,retain就會被發(fā)送給self而非實例變量熄阻,引用計數(shù)器的值也會加1.實例變量的類型不一定非得是對象斋竞。
塊句法內(nèi)存在非實例變量的對象時,如果在棧上生成某個塊對象的副本秃殉,包含的對象就會接受到retain坝初,引用計數(shù)器的值也會增加。
已經(jīng)復(fù)制后钾军,堆區(qū)域中某個塊對象即使收到copy方法鳄袍,結(jié)果也只是塊對象自身的引用計數(shù)器加1.包含的對象的引用計數(shù)器的值不變。
復(fù)制的塊對象在被釋放時吏恭,也會向包含的對象發(fā)送release拗小。
ARC中使用塊對象時的注意事項?
使用ARC開發(fā)軟件時需要注意不要寫死循環(huán)代碼樱哼。使用塊對象時哀九,相關(guān)對象可能會被自動保存,這時也許就會產(chǎn)生死循環(huán)搅幅。