第十四章 塊對象

什么是塊對象

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)搅幅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阅束,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子茄唐,更是在濱河造成了極大的恐慌息裸,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沪编,死亡現(xiàn)場離奇詭異呼盆,居然都是意外死亡,警方通過查閱死者的電腦和手機蚁廓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門访圃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人相嵌,你說我怎么就攤上這事腿时】烁欤” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵圈匆,是天一觀的道長漠另。 經(jīng)常有香客問我,道長跃赚,這世上最難降的妖魔是什么笆搓? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮纬傲,結(jié)果婚禮上满败,老公的妹妹穿的比我還像新娘。我一直安慰自己叹括,他們只是感情好算墨,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著汁雷,像睡著了一般净嘀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侠讯,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天挖藏,我揣著相機與錄音,去河邊找鬼厢漩。 笑死膜眠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的溜嗜。 我是一名探鬼主播宵膨,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炸宵!你這毒婦竟也來了辟躏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤焙压,失蹤者是張志新(化名)和其女友劉穎鸿脓,沒想到半個月后抑钟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涯曲,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年在塔,在試婚紗的時候發(fā)現(xiàn)自己被綠了幻件。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛔溃,死狀恐怖绰沥,靈堂內(nèi)的尸體忽然破棺而出篱蝇,到底是詐尸還是另有隱情,我是刑警寧澤徽曲,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布零截,位于F島的核電站,受9級特大地震影響秃臣,放射性物質(zhì)發(fā)生泄漏涧衙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一奥此、第九天 我趴在偏房一處隱蔽的房頂上張望弧哎。 院中可真熱鬧,春花似錦稚虎、人聲如沸撤嫩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽序攘。三九已至,卻和暖如春寻拂,著一層夾襖步出監(jiān)牢的瞬間两踏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工兜喻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梦染,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓朴皆,卻偏偏與公主長得像帕识,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子遂铡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容