Block筆記

函數(shù)或函數(shù)指針+外部上下文變量 = 閉包,block其實是OC對閉包的實現(xiàn),配合dispatch_queue實現(xiàn)簡單的多線程異步。Dispatch_block_t是GCD提供的無參無返回值的Block語法糖。

block的數(shù)據(jù)結(jié)構(gòu)

struct __block_impl

isa指針:實現(xiàn)對象相關(guān)功能,執(zhí)行時指向&_Stack/Malloc/Global三種類型的地址

flags:附加信息,block copy時會使用(BLOCK_HAS_COPY_DISPOSE、BLOCK_IS_GLOBAL等)invoke:函數(shù)指針,指向block實現(xiàn)的函數(shù)調(diào)用地址

descriptor:附加描述信息(Block_descriptor,包括size、copy和dispose的函數(shù)指針,signature函數(shù)簽名等)

/* variables:block捕獲過來的變量(把變量或變量的地址復(fù)制到了結(jié)構(gòu)體中) */


實現(xiàn)流程:調(diào)用_impl_0構(gòu)造函數(shù)初始化block結(jié)構(gòu)體,賦值回blockTest變量,blockTest->FuncPtr()即執(zhí)行函數(shù)。

/* 具體實現(xiàn)過于復(fù)雜的標記 */^{ blockContent } ();clang -rewrite-obj block.c文件,改寫觀察c語言實現(xiàn):__block_impl,block結(jié)構(gòu)體指針_impl_0,結(jié)構(gòu)體最終實現(xiàn),block的入口(__block_impl,Desc兩個變量 和 impl_0的構(gòu)造函數(shù)賦值isa、Flags、FuncPtr、Desc)

注:{ impl.isa = &_NSConcreteStackBlock; // 被強引用時應(yīng)賦值Global類型 }

_desc_0{ reserved; Block_size; } // block描述,當外部有__block變量需要被捕獲時,desc中會增加copy和dispose函數(shù)指針修改捕獲變量的引用計數(shù)

_func_0{ 函數(shù)實現(xiàn) } // block指向的函數(shù)實現(xiàn)


Block的三種類型

Block有isa指針,所以也有對象的相關(guān)功能,有三種類型,_NSConcrete開頭(ARC下只有兩種)

Global:全局靜態(tài)block,處于data段,不訪問任何外部變量,通過指針安全訪問

Stack:保存在棧中,函數(shù)返回時(block變量作用域結(jié)束)被銷毀,__block變量也會廢棄,所以提供copy功能拷貝到堆,即引入Malloc類型(ARC下自動拷貝到堆,所以會被Malloc取代)

Malloc:保存在堆中,引用計數(shù)為0時銷毀;Block被copy時會復(fù)制到堆中,并經(jīng)過很多步才賦值為malloc。

注:寫在全局作用域時,不獲取任何外部變量時,才是Global類型,否則ARC下自動copy變成Malloc類型,放在堆區(qū)。


Block的copy???

http://www.itdecent.cn/p/eff7130620a9http://www.itdecent.cn/p/bf3798fe3f49Block

會被Copy到堆上的4種情況1、手動[copy];2、block是函數(shù)返回值;3、被強引用(賦值給__strong或id類型);4、含有usingBlock方法的系統(tǒng)API,所以大多情況下都會是Malloc類型。

作為變量:剛聲明時在棧上(如果獲取了外部變量),賦值給非weak的變量(被強引用)即copy到堆上。

作為屬性:strong/copy修飾的屬性(被強引用)會被copy,weak和assign不會。

函數(shù)中:作為參不會被copy,作為返回值會被copy(因為被retain)。

自動copy:ARC下,Block先創(chuàng)建在棧上,如果獲取了外部變量,會通過objc_retainBlock把block拷貝到堆上,并通過objc_autoreleaseReturnValue把堆上的block放入自動釋放池。

注:stack到malloc是深拷貝,malloc到malloc是淺拷貝。


變量的捕獲

http://www.itdecent.cn/p/90dfdd73f431

基礎(chǔ)過程:impl_0結(jié)構(gòu)體中會增加一個同名變量,并在構(gòu)造函數(shù)中以:param(_param)用_param初始化捕獲到的成員變量到結(jié)構(gòu)體中保存起來。在最終函數(shù)執(zhí)行的代碼func_0中,通過__cself->intValue取出變量。

捕獲方式:是值的copy,復(fù)制到數(shù)據(jù)結(jié)構(gòu)中,只能訪問,無法修改。

注:上述基礎(chǔ)的捕獲?局部變量的過程是不能在block內(nèi)部更改變量的值的,但有三種可以:全局變量extern,靜態(tài)變量static,全局靜態(tài)變量extern static。原因:全局變量作用域大,block訪問和普通函數(shù)訪問沒有區(qū)別;靜態(tài)變量是指針傳遞(而非值傳遞)。


__block(和靜態(tài)變量一樣,指針傳遞)

原理:被__block修飾符修飾的變量會實現(xiàn)一個__Block_byref_param_0結(jié)構(gòu)體,封裝外部變量,保存了變量的引用;還會增加_copy_0和_dispose_0兩個方法維護引用計數(shù);所以可以更改外部變量的值。注1:__block會在實現(xiàn)中加一個__Block_byref_i_0結(jié)構(gòu)體指針存捕獲的變量的引用,所以會更改外部的值,并且,它會在desc中增加copy和dispose函數(shù)來修改引用計數(shù)。

注2:__Block_byref_intValue_0結(jié)構(gòu)體儲存了外部變量,內(nèi)部有個__forwarding指針指向自己,copy時會把這個指針指向堆上。

__forwarding:指向捕獲變量自身的指針,block拷貝到堆時會指向堆區(qū)的捕獲變量結(jié)構(gòu)體,用于解決局部變量超出作用范圍時block也能訪問的問題,而這也是block需要copy到堆上的意義。


block中對象變量的內(nèi)存管理

http://www.itdecent.cn/p/f222dc15b0e4

__block

當捕獲的變量obj從棧上拷貝到堆上時,assign被調(diào)用,block持有obj;當obj引用計數(shù)為0被釋放時,dispose被調(diào)用,block對obj的引用也消失。所以__block捕獲的變量可以對其進行適當?shù)膬?nèi)存管理。

__weak

變量的作用域結(jié)束后,內(nèi)存被釋放,block捕獲到的變量指向nil,即不會強引用原對象(用于打破和self的循環(huán)引用)

注:id array = [[NSArray alloc] init]; blk = [^(id obj) { // blockContent } copy];? // 因為函數(shù)結(jié)束時,捕獲的變量作用域結(jié)束,block會被直接銷毀,所以要手動copy。


循環(huán)引用

原因:block會拷貝對象變量的指針引用計數(shù)+1,self被捕獲后被強引用,此時如果self也持有block那就會導(dǎo)致循環(huán)引用。

解決:外部聲明一個self的弱引用weakSelf,block不持有self,打破循環(huán)引用。注:__block修飾后,在block內(nèi) = nil,也可以釋放對變量的強引用打破循環(huán)引用,但如果block不調(diào)用就內(nèi)存泄漏了。


與delegate的對比

1、block是讓代碼塊以閉包(一個函數(shù)+其執(zhí)行的外部上下文變量)的形式傳遞內(nèi)容,實在是太輕量級了,適用于大多數(shù)異步和簡單的回調(diào)。

2、當有多個方法回調(diào)時應(yīng)當選用delegate會更清晰,如UITableView的delegate代理方法。

3、block會涉及到棧區(qū)到 堆區(qū)的拷貝等操作,delegate只是定義了一個方法列表,在遵守了協(xié)議的對象的objc_protocol_list中添加了一個節(jié)點,運行時向?qū)ο蟀l(fā)送消息即可。所以block在時間空間消耗都大于delegate,性能消耗較大。

4、代理更加面向過程,block更加面向結(jié)果。


Block和函數(shù)指針的區(qū)別

函數(shù)指針僅僅是一個地址,不具備函數(shù)原型信息,沒有類型限制,比如一個指向變量的指針同樣可以指向一個函數(shù),但是?block?作為函數(shù)對象,是有部分函數(shù)信息的,類型限制更明確。

block?方式便于實現(xiàn)真正的?“函數(shù)式”?編程,讓函數(shù)成為基本的運算元,往更遠的方向說,真正的函數(shù)式語言可以去掉寄存器(請參考馮諾依曼機器基本架構(gòu)),提高程序的執(zhí)行效率,近段時間的語言都支持?lambda?語法,包括JS、?C++?、?Python?、?Ruby等,可見各個編程語言為改進馮諾依曼架構(gòu)做出的努力和準備。

提高程序的健壯性, 定義函數(shù)的代碼會位于程序的代碼段,如果函數(shù)內(nèi)部出現(xiàn)內(nèi)存溢出,就會直接導(dǎo)致?crash,因為代碼段是不可寫的;block?作為函數(shù)對象在運行時生成,位于棧內(nèi),即使出現(xiàn)內(nèi)存溢出,一般也不會直接導(dǎo)致?crash。


相關(guān)問答

請描述一下Block的底層結(jié)構(gòu)

_block_impl結(jié)構(gòu)體即Block的實現(xiàn),包括isa,funcPtr,flag,reserved,descriptor,捕獲的變量。


Block的三種類型,何時從棧copy到堆

不捕獲變量時:_NSConcreteGlobalBlock;剛創(chuàng)建且捕獲了變量時在棧上:stack;被強引用或作為函數(shù)返回值時,copy到堆:malloc,以便棧上的作用域結(jié)束后扔能訪問到Block。所以一般用copy修飾,語義明確。


Block變量的捕獲,__weak和__block

Block會捕獲外部變量時有三種情況:auto——基礎(chǔ)數(shù)據(jù)值拷貝,指針類型指針拷貝,會在數(shù)據(jù)結(jié)構(gòu)中增加一個同名變量保存;__block——獲取地址強持有變量,引用計數(shù)+1,封裝成一個結(jié)構(gòu)體byref,并增加assign和dispose函數(shù)維護引用計數(shù);__weak——通過一個弱引用指針捕獲對象,不強持有,一般用于打破block捕獲變量造成的循環(huán)引用。__weak id weakObj;


forward指針的作用

block從棧上拷貝到堆上時,byref結(jié)構(gòu)體中指向自身的forward指針會指向堆上的變量結(jié)構(gòu)體對象。保證無論在棧上還是堆上都能通過byref->forward->obj訪問到捕獲的變量。

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

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