iOS:Block __block修飾符

__block修飾符

上一篇文章中說過,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的類型

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變量在棧上時,不會對指向的變量產生強引用。

當__block變量copy到堆上時,會根據這個變量的修飾符是__strong,__weak,__unsafe_unretained作出相應的操做造成強引用(retain)或者弱引用。(ARC會retain,MRC不會)。

__forwarding指針

最後說一下上面的__forwarding指針問題。

這個圖能夠很好地詮釋這個問題了。

咱們的block在內存中可能位於棧上,可能在堆上。

使用了這個指針以後,讓咱們在block位於不一樣內存位置的狀況下,訪問到相應準確位置的變量。

相關文章
相關標籤/搜索