第十四章 塊對象

什么是塊對象

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ù)列則可以設置為(void),此外也可以將參數(shù)列連同括號全部省略,或者只保留括號。

此外,剋通過_ _BLOCKS_ _宏來檢查當前系統(tǒng)環(huán)境下是否可使用塊對象。根據(jù)編譯的不同條件,即可區(qū)別可以使用塊對象時的情況和不可以使用塊對象的情況。

塊對象中的變量行為

塊對象只在塊句法中保存自動變量的值。

塊對象就是把可以執(zhí)行的代碼和代碼中可訪問的變量“封裝”起來,使得之后可以做進一步處理的包。而閉包這個稱呼本身就是把變量等執(zhí)行環(huán)境封裝起來的意思。我們把閉包引用,讀取外部變量稱為捕獲(capture)。

總結:

塊句法主題中,除塊句法內(nèi)部的局部變量和形參外,還包括當前位置處可以訪問的變量。這些變量中有外部變量,還有包含塊句法的代碼塊內(nèi)可以訪問的局部變量。

從塊對象內(nèi)部可以直接訪問外部變量和靜態(tài)變量(static變量),也可以直接改變變量的值。

在包含塊句法的代碼塊內(nèi)可訪問的局部變量中,書寫句法塊時自動變量(棧內(nèi)變量)的值會被保存起來,然后再被訪問。

所以,即使自定變量最初的值發(fā)生了變化,塊對象在使用時也不會知道。

自動變量的值可以被讀取但是不能被改變。

自動變量為數(shù)組時,會發(fā)生編譯錯誤。

簡言之,在塊對象中雖然可以使用可訪問的變量,但自動變量的話就只能讀取復制值。換言之,自動變量在運行時就相當于const修飾的變量。

排序函數(shù)和塊對象

塊對象可以實現(xiàn)和函數(shù)指針相同的功能。使用函數(shù)指針時,需要寫不同的函數(shù)來應對各種不同的功能,此外,為了給函數(shù)傳遞需要的附加信息,往往還要使用多余的參數(shù)和外部變量,而這些都違背了編寫代碼要盡可能獨立易懂的原則。

通過靈活使用塊對象,我們就可以將這樣的函數(shù)或方法實現(xiàn)為易讀,靈活的書寫方式。

塊對象的構成

塊對象的實例和生命周期

在編譯塊句法時,會生成存放必要信息的內(nèi)存地址(實為結構體)和函數(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;

}

塊對象將要保存的自動變量的信息復制到了內(nèi)存區(qū)域。該內(nèi)存區(qū)域也包含了評估塊對象時所執(zhí)行的函數(shù)指針等的信息。

即使反復執(zhí)行塊句法處的代碼,也不會每次都為塊對象動態(tài)分配一片新的內(nèi)存區(qū)域。但是,被復制到內(nèi)存區(qū)域中的自動變量的值每次都會更新。另一方面,含塊句法的函數(shù)在遞歸調(diào)用時,同自動變量相同,塊對象就會在棧上保存多個內(nèi)存區(qū)域。

總結:

塊句法寫在函數(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)中,當函數(shù)內(nèi)的塊句法不包含自動變量時,就沒必要復制值,所以塊對象會被設置在靜態(tài)數(shù)據(jù)區(qū)。但因為實現(xiàn)方法可能改變,應該避免編寫具有這種依賴關系的程序。

應該避免的編碼模式

上面注釋處代碼之所以會發(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é)中所說的復制。但是,使用ARC時操作是不同的。

塊對象的復制

有一個函數(shù)可以復制塊對象到新的堆區(qū)域。通過使用該功能,即使是函數(shù)內(nèi)的塊對象也能獨立于棧被持續(xù)使用。此外,還有一個函數(shù)可以釋放不需要的塊對象。

Block_copy(block)

參數(shù)為棧上的塊對象時,返回堆上復制的塊對象。否則(參數(shù)為靜態(tài)數(shù)據(jù)區(qū)或為堆上的塊對象)則不進行復制而直接將參數(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變量的塊對象在被復制后,新生成的塊對象也能共享_ _block變量的值。

4.多個塊對象訪問一個_ _block變量時,只要有一個塊對象存在著,_ _block變量就會隨之存在。如果訪問_ _block變量的塊對象都不在了,_ _block也會隨之消失。

因為可能會涉及到實現(xiàn),這里省略了對_ _block變量行為的說明。但有一點,隨著塊對象的復制,_ _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ù)結果是不正確的。

ARC和塊對象

使用ARC和不使用ARC時,塊對象的操作是有區(qū)別的。

在ARC中,需要保存塊對象時,編譯器會自動插入copy操作。具體的說,就是在被帶入強訪問變量以及被作為return的返回值返回的時候。這些情況下,程序不需要顯式地執(zhí)行塊對象的副本。

但是,作為方法參數(shù)傳入的塊對象是不會自動執(zhí)行copy的。而且,當塊對象聲明為屬性值時,屬性選項一般會指定(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ā)生改變。

接下來塊對象自身被復制,并在堆區(qū)域中生成了新的塊對象。(圖b)。這里,實例對象的引用計數(shù)加1,由于方法執(zhí)行結束后自動變量obj也會消失,因此引用計數(shù)加1就使得塊對象成為了所有者。實例對象不是被復制,而是被共享,不只從塊對象,從哪都可以發(fā)送消息。

需要注意的是在某個類方法內(nèi)的塊句法被書寫了同一類的實例變量這種情況。如下面這個例子,假設ivar為包含方法someMethod的類實例變量。

void(^cp)(void);

- (void)someMethod {

intn =10;

void(^block)(void) = ^{ [ivar calc:n]; };

...

cp = [block copy];

}

這種情況下,當塊對象被復制時,self的引用計數(shù)將加1,而非ivar。如下圖所示,方法的引用參數(shù)self在堆上分配。在上例中,self好像沒有出現(xiàn)在塊句法中,我們可以按下面方式理解:

^{ [self->ivar calc:n]; };

塊句法內(nèi)的實例變量為整數(shù)或者實數(shù)時也是一樣的,self的引用計數(shù)也會增加。也就是說,當與self對等的對象不存在時,所有的實例變量都將不能訪問。

塊對象和對象的關系總結如下:?

方法定義內(nèi)的塊句法中存在實例變量時,可以直接訪問實例變量,也可以改變其值。

方法定義內(nèi)的塊句法中存在實例變量時,如果在棧上生成的塊對象的副本,retain就會被發(fā)送給self而非實例變量,引用計數(shù)器的值也會加1.實例變量的類型不一定非得是對象。

塊句法內(nèi)存在非實例變量的對象時,如果在棧上生成某個塊對象的副本,包含的對象就會接受到retain,引用計數(shù)器的值也會增加。

已經(jīng)復制后,堆區(qū)域中某個塊對象即使收到copy方法,結果也只是塊對象自身的引用計數(shù)器加1.包含的對象的引用計數(shù)器的值不變。

復制的塊對象在被釋放時,也會向包含的對象發(fā)送release。

ARC中使用塊對象時的注意事項?

使用ARC開發(fā)軟件時需要注意不要寫死循環(huán)代碼。使用塊對象時,相關對象可能會被自動保存,這時也許就會產(chǎn)生死循環(huán)。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容