ObjC block簡析(一)

探究block的本質

Block

在main.m的main函數中聲明一個block並執行block()經過xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp命令將main.m轉爲main.cpp。xcode

cpp文件中的main函數

在main.cpp中能夠發現上圖中的代碼。其中在main函數中的block和block(),被轉化爲bash

void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
複製代碼

其中不乏強制轉換類型的代碼,將強制類型轉換的代碼去掉咱們能夠明確的看出:iphone

void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
        block->FuncPtr(block);
複製代碼

block存儲了__main_block_impl_0函數的地址,而此函數中傳遞的第一個值是一個函數指針,函數的做用就是block中存儲的代碼的做用,即打印hello world。第二個參數是一個結構體指針,其中包含有對block的描述。函數

__main_block_impl_0是一個結構體,__main_block_impl_0函數是結構體__main_block_impl_0的構造函數,在此結構中就能夠看到這個與結構體名字相同的沒有返回值的函數。post

該函數將類型地址賦值給isa,將存放block中代碼的函數的指針賦值給funcptr,將block的描述賦值給desc。spa

__main_block_impl_0結構體中第一個成員就是: 3d

__block_impl結構體
咱們能夠看到這裏並非結構體指針,而是結構體自己,因此 __main_block_impl_0的第一個成員實際上是isa.

而咱們的block()轉換成了block->FuncPtr(block);就是經過block查找到FuncPtr而後調用,執行相應的代碼。至於block明明是__main_block_impl_0類型的爲何卻能直接查找到FuncPtr成員,上面已經說明這裏存放的並非結構體指針,也不是地址,而是結構體impl自己,因此block是能夠通過類型強制轉換轉換成__block_impl類型進而經過FuncPtr指針調用相應的函數的。指針

咱們都知道,ObjC中的對象都有一個isa指針,而block中一樣存在isa指針。因此能夠說block是一個ObjC指針。它封裝了block函數的調用。code

Block的變量捕獲

像上述例子對咱們來講一般沒有什麼實際做用,而咱們一般要在block中處理一下外部變量的邏輯,那麼block是怎麼處理這些變量的呢?cdn

auto局部變量的捕獲

訪問auto變量

出現上述狀況的緣由是什麼呢?依然將main.m轉成main.cpp。

main.cpp

上面圖片中咱們能夠發現block的結構體中出現了一個age變量,而且其構造函數代表將_age賦值給age成員。而在FuncPtr存儲的函數__main_block_func_0的地址中,咱們發現調用此函數是從block的結構體中取出age成員的值進行打印的。因此當定義block的時候age的值已經被block用一個一樣名字的成員捕獲了。並且捕獲的僅僅是age的值。

static局部變量的捕獲

main.cpp

經過上圖咱們能夠看出block中存儲的是a的地址,因此當FuncPtr指向的函數調用的時候會經過取a地址中存儲的值,所就出現下面這種狀況了。

static類型的變量捕獲

全局變量的捕獲

全局變量

咱們能夠看出全局變量並無被block所捕獲,由於全局變量存放在全局區,隨時均可以訪問,因此當FuncPtr指向的函數調用的時候就會直接取a和b的值用。而局部變量超過做用域就會自動回收因此block須要在自身存放一份,以保證其能準確訪問。

總結

局部變量在block中使用的時候會被block捕獲,auto變量是值捕獲,而static變量是地址捕獲。所有變量不會被捕獲。

block類型

既然上面說block是一個oc對象,那麼他也應該是有類型的。那麼block的類型有什麼有趣的事呢?

因爲在ARC環境下編譯器默默對block作了一些咱們看不見的工做,因此咱們將xcode的arc模式關掉,以便於窺探到本質。

關閉arc

類型

結果

經過上述結果咱們能夠看出當block訪問了auto變量的時候會變成__NSStackBlock__類型。而其餘狀況下是__NSGlobalBlock__類型。

__NSGlobalBlock__類型存在於數據區,__NSStackBlock__存在於棧區。

在ARC環境下打印的結果:

ARC下的block類型

而在ARC環境下本來__NSGlobalBlock__的block依然是__NSGlobalBlock__類型,而本來是__NSStackBlock__卻變成了__NSMallocBlock__存放在堆區。這是由於當咱們定義block的時候ARC默認爲咱們作了一次copy操做。

下面是block的類型以及在內存中存放的區域:

block的類型

咱們嘗試在MRC下對__NSGlobalBlock____NSMallocBlock__類型的block進行copy操做並打印結果:

copy操做

block的類型(MRC環境)

訪問了auto變量的block是__NSStackBlock__類型,沒有訪問auto變量的block是__NSGlobalBlock__類型。而對__NSStackBlock__類型進行copy操做就會變爲__NSMallocBlock__類型。

對三種類型的block分別進行copy操做結果以下:

對三種類型的block進行copy操做


ObjC block簡析(二)

相關文章
相關標籤/搜索