上一篇文章中說過,auto類型的局部變量,能夠被block捕獲,可是不能修改值。函數
__block能夠解決block內部沒法修改外部auto變量的問題。測試
__block int age = 10; void (^myblock)(void) = ^{ NSLog(@"%d",age); }; age = 20; myblock();
用法就是這麼簡單,這樣咱們修改age爲20的時候,打印也是20。spa
咱們看看編譯後的代碼。debug
struct __Block_byref_age_0 { void *__isa; __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; }; //Block struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
在block內部多了一個指向__Block_byref_age_0
類型結構體的age指針。上面我也帖上了這個__Block_byref_age_0
結構體的結構。咱們發現int類型的age在這個結構體內部了。指針
那也就是說,__block修飾的變量,編譯器會把它包裝成一個對象,而後咱們的這個成員變量放到了這個對象的內部。code
咱們觀察一下這個__Block_byref_age_0
內部,這些變量可能有疑惑的也就是這個__forwarding
。他是一個指向這個結構體自身的指針。並且咱們還能夠看出來在打印age的時候,是也是經過__forwarding調用的age(age->__forwarding->ag
),具體爲何要多加這個字段,咱們後面再說。對象
static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); }
__main_block_desc_0
這個結構體中也多了兩個指針,這是與內存管理有關的函數。生命週期
底層分析差很少了,那咱們還沒說到爲何__block修飾的屬性,在block內部能夠修改,咱們看下面的代碼內存
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; void (*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); (age.__forwarding->age) = 20;
咱們建立了__Block_byref_age_0
類型的age對象,同時把外部age的值也就是10,傳遞了進去。而後初始化了block。rem
關鍵是下面的修改age的值的時候,直接就是修改的age對象裏面的age屬性了,而後打印的時候,也是打印的他。
這個地方其實仍是挺抽象的了,也不是很好理解。
怎麼前面定義的age變量跟後面修改的就不是一個了?
__block int age = 10; NSLog(@"%p",&age); void (^myblock)(void) = ^{ NSLog(@"%d",age); }; NSLog(@"%p",&age); age = 20; myblock();
這是最簡單的方法,打印出兩個age的地址,就是不同的。那咱們怎麼去判斷就是__Block_byref_age_0
裏面的age呢,你們能夠參考下面的作法。
struct __Block_byref_age_0 { void *__isa; struct __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; }; struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(void); void (*dispose)(void); }; struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl;// 8+4+4+8 struct __main_block_desc_0* Desc;//8 struct __Block_byref_age_0 *age;// 8+8+4+4 }; int main(int argc, const char * argv[]) { @autoreleasepool { __block int age = 10; void (^block)(void) = ^{ age = 20; NSLog(@"age is %d", age); }; struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block; NSLog(@"%p", &age); } return 0; }
上面的操做就是咱們把底層的一些結構拿出來,而後把咱們的block類型橋接成__main_block_impl_0
類型。而後咱們經過debug能夠拿到這個blockImpl的地址,而後經過內存中的地址偏移計算出來內部__Block_byref_age_0
中的age地址,看看和打印出來的age地址是否一致。
咱們拿到blockImpl的地址是0x1005002d0(我測試時候的值,每次都不一樣)。
__main_block_impl_0
內部第一個屬性是一個__block_impl
結構體,而後__block_impl
內部兩個指針(一個指針8字節)兩個int(一個int4字節),共佔24字節。
第二個參數是一個指針,8字節。
age在第三個參數指向的結構體中,也就是說__Block_byref_age_0
類型的age內存地址是0x1005002d0偏移32,也就是0x100500300。而後__Block_byref_age_0
內部的age變量前面,有兩個指針兩個int,24字節,0x100500300再偏移24,也就是0x100500318。跟咱們NSLog(@"%p", &age);
打印的一致。
因此能夠得出,咱們修改的這個age,其實就是底層age對象內部的age變量。
上面咱們留下了一個__forwarding
指針的疑問,咱們先不着急解決,先說說block類型。
block有isa指針,開始我想經過寫了幾個類型的block,用clang編譯看cpp代碼,但發現一直是這個樣子impl.isa = &_NSConcreteStackBlock;
。因此我就用最直接的打印[obj class]的方法。
注意,由於在ARC的環境下,編譯器給咱們作了不少內存相關的工做,因此我在研究block類型的過程當中切換到了MRC環境。
我用過的例子就不寫了,下面是一個小總結。
一共有三種Block
__NSGlobalBlock__ 內存位於數據區
__NSStackBlock__ 棧區
__NSMallocBlock__ 堆區
具體什麼樣的block對應哪種類型?
__NSGlobalBlock__
:沒有訪問auto變量__NSStackBlock__
:訪問了auto變量__NSMallocBlock__
: __NSStackBlock__
調用copy
提示
咱們在聲明一個block屬性的時候,習慣用copy關鍵字,是爲了把棧區的block拷貝到堆區,讓咱們來管理他的生命週期。
ARC環境下會根據狀況自動將棧上的block拷貝到堆上。ARC環境下也用copy是爲了和MRC環境統一,也能夠用strong。
當__block變量在棧上時,不會對指向的變量產生強引用。
當__block變量copy到堆上時,會根據這個變量的修飾符是__strong,__weak,__unsafe_unretained作出相應的操做造成強引用(retain)或者弱引用。(ARC會retain,MRC不會)。
最後說一下上面的__forwarding指針問題。
這個圖能夠很好地詮釋這個問題了。
咱們的block在內存中可能位於棧上,可能在堆上。
使用了這個指針以後,讓咱們在block位於不一樣內存位置的狀況下,訪問到相應準確位置的變量。