淺談OC中Block的本質

Block簡介

  • block是將函數及其執行上下文封裝起來的一個對象
  • block實現的內部,有不少變量,由於block也是一個對象
  • 其中包含了諸如isa指針,imp指針等對象變量,還有儲存其截獲變量的對象等
  • 原文博客地址: 淺談OC中Block的本質

定義和使用

block根據有無參數和有無返回值有如下幾種簡單使用方式html

// 無參數無返回值
void (^ BlockOne)(void) = ^(void){
    NSLog(@"無參數,無返回值");  
};  
BlockOne();//block的調用

// 有參數無返回值
void (^BlockTwo)(int a) = ^(int a){
    NSLog(@"有參數,無返回值, 參數 = %d,",a);
};  
BlockTwo(100);

// 有參數有返回值
int (^BlockThree)(int,int) = ^(int a,int b){    
    NSLog(@"有參數,有返回值");
    return a + b; 
};  
BlockThree(1, 5);

// 無參數有返回值
int(^BlockFour)(void) = ^{
    NSLog(@"無參數,有返回值");
    return 100;
};
BlockFour();
複製代碼

但是以上四種block底層又是如何實現的呢? 其本質到底如何? 接下來咱們一塊兒探討一下c++

Block的本質

  • 爲了方便咱們這裏新建一個Command Line Tool項目, 在main函數中執行上述中一個block
  • 探索Block的本質, 就要查看其源碼, 這裏咱們使用下面命令把main.m文件生成與其對應的c++代碼文件
  • main.m文件所在的目錄下, 執行以下命令, 會生成一個main.cpp文件
  • main.cpp文件添加到項目中, 並使其不參與項目的編譯, 下面咱們就具體看一下block的底層究竟是如何實現的
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
複製代碼

打開main.cpp文件, 找到文件最底部, 能夠看到block的相關源碼以下程序員

// block的結構體
struct __main_block_impl_0 {
  // 結構體的成員變量
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // 構造函數
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// 封裝了block執行邏輯的函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_11c959_mi_0);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        // 定義block變量
        void (* BlockOne)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        // 執行block內部的源碼
        ((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne);

    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
複製代碼

其中block的聲明和調用的對應關係以下bash

image

刪除其中的強制轉換的相關代碼後微信

// 定義block變量
void (* BlockOne)(void) = &__main_block_impl_0(
                                                (void *)__main_block_func_0,
                                                &__main_block_desc_0_DATA
                                            );

// 執行block內部的源碼
BlockOne->FuncPtr(BlockOne);
複製代碼

上述代碼中__main_block_impl_0函數接受兩個參數, 並有一個返回值, 最後把函數的地址返回給BlockOne, 下面找到__main_block_impl_0的定義markdown

// 結構體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // c++中的構造函數, 相似於OC中的init方法
  // flags: 默認參數, 調用時可不傳
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製代碼
  • __main_block_impl_0函數中的第一個參數__main_block_func_0賦值給了fp, fp又賦值給了impl.FuncPtr, 也就意味着impl.FuncPtr中存儲的就是咱們要執行的__main_block_func_0函數的地址
  • Block結構體中的isa指向了_NSConcreteStackBlock, 說明Block是一個_NSConcreteStackBlock類型, 具體後面會詳解
  • __main_block_impl_0函數中的第二個參數__main_block_desc_0_DATA
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
複製代碼
  • 其中reserved賦值爲0
  • Block_size被賦值爲sizeof(struct __main_block_impl_0), 即爲__main_block_impl_0這個結構體佔用內存的大小
  • __main_block_impl_0的第二個參數, 接受的即爲__main_block_desc_0結構體的變量(__main_block_desc_0_DATA)的地址

Block變量捕獲

image

  • 局部變量分爲兩大類: autostatic
    • auto: 自動變量, 離開做用域就會自動銷燬, 默認狀況下定義的局部變量都是auto修飾的變量, 系統都會默認給添加一個auto
    • auto不能修飾全局變量, 會報錯
    • static做用域內修飾局部變量, 能夠修飾全局變量
  • 全局變量

局部變量

auto變量捕獲

auto局部變量在Block中是值傳遞iphone

下述代碼輸出值爲多少?函數

int age = 10;

void (^BlockTwo)(void) = ^(void){
    NSLog(@"age = %d,",age);
};

age = 13;
BlockTwo();
// 輸出10
複製代碼

輸出值爲何是10而不是13呢? 咱們仍是生成main.cpp代碼看一下吧, 相關核心代碼以下oop

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 這裏多了一個age屬性
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_80d62b_mi_0,age);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;  
        // 定義屬性
        int age = 10;

        // block的定義
        void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        
        // 改變屬性值
        age = 13;
        // 調用block
        ((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
複製代碼

那麼下面咱們一步步看一下, 吧一些強制轉換的代碼去掉以後spa

int age = 10;

void (*BlockTwo)(void) = &__main_block_impl_0(
                                            __main_block_func_0,
                                            &__main_block_desc_0_DATA,
                                            age
                                            );

age = 13;
BlockTwo->FuncPtr(BlockTwo);
複製代碼

在上面的__main_block_impl_0函數裏面相比於以前的, 多了一個age參數

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 新的屬性age
  int age;
  // 構造函數, 多了_age參數
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製代碼
  • 上面的構造方法__main_block_impl_0中, 多了一個_age參數
  • 同時後面多了一條age(_age)語句, 在c++中, age(_age)至關於age = _age, 即給age屬性賦值, 存儲構造函數傳過來的age屬性的值
  • 因此在後面調用block的時候, block對應的結構體所存儲的age屬性的值仍然是10, 並無被更新
// 及時這裏從新對age進行了賦值
age = 13;

// 這裏調用BlockTwo的時候, 結構體重的age屬性的值並無被更新
BlockTwo->FuncPtr(BlockTwo);

// 最後在執行block內部邏輯的時候, 
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age; // bound by copy
    // 這裏的age, 仍然是block結構體中的age, 值並無改變, 因此輸出結果仍是10
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_80d62b_mi_0,age);
}
複製代碼

static變量捕獲

static局部變量在Block中是指針傳遞, 看一下下面代碼的輸出狀況

auto int age = 10;
static int weight = 20;

void (^BlockTwo)(void) = ^(void){
    NSLog(@"age = %d, weight = %d,",age, weight);
};

age = 13;
weight = 23;
BlockTwo();
複製代碼
  • 上面代碼輸出結果: age = 10, weight = 23
  • 從新賦值後age的結果不變, 以前已經說過了
  • 但是weight的結果倒是賦值後的結果, 至於爲何, 請繼續向下看吧...
  • 咱們仍是生成main.cpp代碼看一下吧, 相關核心代碼以下
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  int *weight;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  int *weight = __cself->weight; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_282a93_mi_0,age, (*weight));
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        auto int age = 10;
        static int weight = 20;

        void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &weight));

        age = 13;
        weight = 23;
        ((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
複製代碼
  • 從上面代碼能夠看到__main_block_impl_0類中多了兩個成員變量ageweight, 說明兩個變量咱們均可以捕獲到
  • 不一樣的是, 一樣都是int變量, 使用不一樣的修飾詞修飾, __main_block_impl_0類中也是不一樣的
  • static修飾的變量weightblock中存儲的是weight的地址, 在後面的block函數中咱們使用的也是其地址
int age;
int *weight;

// &weight
void (*BlockTwo)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age, &weight);

// 下面構造方法中, 一樣(weight(_weight)方法以前講過)將傳過來的weight的地址賦值給了 (int *weight;)
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
}
複製代碼
  • 也就是說上面的構造函數中
    • age保存的是一個準確的值
    • weight保存的是weight所在的內存地址
  • 因此在最後調用block內部邏輯的時候
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int age = __cself->age; // bound by copy
    int *weight = __cself->weight; // bound by copy

    // (*weight)至關於從weight的內存地址中取值, 在執行操做
    // 然而weight內存中的值已經在後面賦值的時候被更新了, 因此這裏取出的值是賦值後的
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_282a93_mi_0,age, (*weight));
}
複製代碼
  • 也就是說, 一樣是局部變量
  • auto修飾的變量在block中存儲的是變量的值(值傳遞)
  • static修飾的變量在block中存儲的是變量的內存地址(地址傳遞)

全局變量

int age = 10;
static int weight = 20;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^BlockTwo)(void) = ^(void){
            NSLog(@"age = %d, weight = %d,",age, weight);
        };
        
        age = 13;
        weight = 23;
        BlockTwo();
    }
    return 0;
}
複製代碼

上面代碼的輸出結果, 毫無疑問是13和23, 相關c++代碼以下

int age = 10;
static int weight = 20;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            // 封裝了block執行邏輯的函數
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_0ee0bb_mi_0,age, weight);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        // 定義block變量
        void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        age = 13;
        weight = 23;
        ((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
複製代碼
  • 從上面代碼能夠看出__main_block_impl_0結構體重並無捕獲到ageweight的成員變量
  • 一樣在定義block變量的時候中也不須要傳入ageweight的變量
  • 在封裝了block執行邏輯的函數中, 就能夠直接使用全局的變量便可

Block的類型

Block的三種類型

  • 在以前的C++源碼中, __main_block_impl_0結構體中isa指向的類型是_NSConcreteStackBlock
  • 下面就具體看一下, Block的只要類型有那些
  • 先看一下下面這部分代碼的輸出結果
void (^block)(void) = ^(void){
    NSLog(@"Hello World");
};

NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);

/*
 2019-06-24 15:46:32.506386+0800 Block[3307:499032] __NSGlobalBlock__
 2019-06-24 15:46:32.506578+0800 Block[3307:499032] __NSGlobalBlock
 2019-06-24 15:46:32.506593+0800 Block[3307:499032] NSBlock
 2019-06-24 15:46:32.506605+0800 Block[3307:499032] NSObject
 */
複製代碼
  • block的類型NSBlock最終也是繼承自NSObject
  • 這也能夠解釋爲何block的結構體__main_block_impl_0中會有一個isa指針了
  • 此外, block共有三種類型, 能夠經過調用class方法或者isa指針查看具體類型, 最終都是繼承自NSBlock類型
    • __NSGlobalBlock__或者_NSConcreteGlobalBlock
    • __NSStackBlock__或者_NSConcreteStackBlock
    • __NSMallocBlock__或者_NSConcreteMallocBlock

block在內存中的分配

  • _NSConcreteGlobalBlock: 在數據區域
  • _NSConcreteStackBlock: 在棧區域
  • _NSConcreteMallocBlock: 在堆區域

內存分配圖

  • 應用程序的內存分配圖如上圖所示, 自上而下依次爲內存的低地址-->內存的高地址
  • 程序區域: 代碼段, 用於存放代碼
  • 數據區域: 數據段, 用於存放全局變量
  • 堆: 動態分配內存,須要程序員本身申請,程序員本身管理, 一般是alloc或者malloc方式申請的內存
  • 棧: 用於存放局部變量, 系統會自動分配內存, 自動銷燬內存

區分不一樣的block類型

  • 上面提到, 一共有三種block類型, 且不一樣的block類型存放在內存的不一樣位置
  • 可是如何區分所定義的block 究竟是哪種類型呢 看看下面代碼的執行狀況, 運行環境實在MRC環境下
static int age = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int weight = 21;
        void (^block1)(void) = ^(void){
            NSLog(@"Hello World");
        };
        void (^block2)(void) = ^(void){
            NSLog(@"age = %d", age);
        };
        void (^block3)(void) = ^(void){
            NSLog(@"age = %d", weight);
        };
        
        NSLog(@"block1 = %@", [block1 class]);
        NSLog(@"block2 = %@", [block2 class]);
        NSLog(@"block3 = %@", [block3 class]);
        
        /* 2019-06-24 21:13:14.555206+0800 Block[30548:1189724] block1 = __NSGlobalBlock__ 2019-06-24 21:13:14.555444+0800 Block[30548:1189724] block2 = __NSGlobalBlock__ 2019-06-24 21:13:14.555465+0800 Block[30548:1189724] block3 = __NSStackBlock__ */
    }
    return 0;
}
複製代碼

針對各類不一樣的block總結以下

block類型 環境
__NSGlobalBlock__ 沒有訪問auto變量
__NSStackBlock__ 訪問了auto變量
__NSMallocBlock__ __NSStackBlock__調用了copy
  • 因爲__NSMallocBlock__是放在堆區域
  • 要想建立出__NSMallocBlock__類型的block, 咱們能夠調用copy方法
void (^block3)(void) = ^(void){
    NSLog(@"age = %d", weight);
};

NSLog(@"block3 = %@", [block3 class]);
NSLog(@"block3 = %@", [[block3 copy] class]);
/* 輸出分別是: block3 = __NSStackBlock__ block3 = __NSMallocBlock__ */
複製代碼
  • 從上面的代碼中咱們能夠明顯看到, __NSStackBlock__類型的block調用copy方法後, 就會變成__NSMallocBlock__類型的block
  • 至關於生成的block是在堆區域的
  • 那麼另外兩種類型調用copy方法後,又會如何? 下面一塊兒來看一下吧
int weight = 21;
void (^block1)(void) = ^(void){
    NSLog(@"Hello World");
};
void (^block3)(void) = ^(void){
    NSLog(@"age = %d", weight);
};

NSLog(@"block1 = %@", [block1 class]);
NSLog(@"block1 = %@", [[block1 copy] class]);
NSLog(@"block3 = %@", [block3 class]);
NSLog(@"block3 = %@", [[block3 copy] class]);
NSLog(@"block3 = %@", [[[block3 copy] copy] class]);
/* __NSGlobalBlock__ __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__ __NSMallocBlock__ */
複製代碼
  • 從上面的代碼能夠看到, 只有__NSStackBlock__類型的block調用copy以後纔會變成__NSMallocBlock__類型, 其餘的都是原類型
  • 主要也是__NSStackBlock__類型的做用域是在棧中, 做用域中的局部變量會在函數結束時自動銷燬
  • __NSStackBlock__調用copy操做後,分配的內存地址至關於從棧複製到堆;副本存儲位置是堆
  • 其餘的則可參考下面表格
Block類 副本源的配置存儲域 複製效果
__NSStackBlock__ 從棧複製到堆
__NSGlobalBlock__ 程序的數據區域 什麼也不作
__NSMallocBlock__ 引用計數增長
  • ARC環境下, 編譯器會根據狀況自動將站上的block複製到堆上, 相似如下狀況
    • block做爲函數返回值時
    • block賦值給__strong修飾的指針時
    • block做爲GCD的方法參數時

__block修飾符

Question: 定義一個auto修飾的局部變量, 並在block中修改該變量的值, 可否修改爲功呢?

auto int width = 10;
static int height = 20;
void (^block)(void) = ^(void){
    // 事實證實, 在Xcode中這行代碼是報錯的
    width = 22;
    // 可是static修飾的變量, 倒是能夠賦值, 不會報錯
    height = 22;
    NSLog(@"width = %d, height = %d", width, height);
};

block();

// width = 10, height = 22
複製代碼
  • 在以前提到, 在block中, auto修飾的變量是值傳遞
  • static修飾的變量是指針傳遞, 因此在上述代碼中, block存儲的只是height的內存地址
  • 一樣auto變量實在main函數中定義的, 而block的執行邏輯是在__main_block_func_0結構體的方法中執行的, 至關於局部變量不能跨函數訪問
  • 至於static修飾的變量爲何能夠修改?
    • __main_block_impl_0結構體中height存儲的是其內存地址, 在其餘函數或者結構體中訪問和改變height的方式都是經過其真真訪問的
    • 相似賦值方式: (*height) = 22;
    • 取值方式: (*height)

__block修飾auto變量

__block auto int width = 10;

void (^block)(void) = ^(void) {
    // 很明顯, 這裏就能夠修改了
    width = 12;
    NSLog(@"width = %d", width);
};

block();
// width = 12
複製代碼

爲何上面的代碼就能夠修改變量了呢, 這是爲何呢.....請看源碼

下面是生成的block的結構體

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // 這裏的width被包裝成了一個__Block_byref_width_0對象
  __Block_byref_width_0 *width; // by ref
  // 這裏能夠對比一下以前的未被__block修飾的int變量
  // int width;
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_width_0 *_width, int flags=0) : width(_width->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製代碼
  • 上述代碼看到__block能夠用於解決block內部沒法修改auto修飾的變量值得問題
  • 可是__block不能修飾全局變量和static修飾的靜態變量(一樣也不須要, 由於在block內部能夠直接修改)
  • 通過__block修飾的變量會被包裝成一個對象(__Block_byref_width_0)
  • 下面是width被包裝後的對象的結構體, 在結構體內, 會有一個width成員變量
struct __Block_byref_width_0 {
  void *__isa;
  // 一個指向本身自己的成員變量
  __Block_byref_width_0 *__forwarding;
  int __flags;
  int __size;
  // 外部定義的auto變量
  int width;
};
複製代碼

下面咱們先看一下, autoblock的定義和調用

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        // __block auto int width = 10;
        auto __Block_byref_width_0 width = {
            0,
            &width,
            0,
            sizeof(__Block_byref_width_0),
            10
        };

        void (*block)(void) = &__main_block_impl_0(
            __main_block_func_0,
            &__main_block_desc_0_DATA,
            &width,
            570425344
        );

        block->FuncPtr(block);
    }
    return 0;
}
複製代碼
  • 能夠看到在定義的__Block_byref_width_0類型的width中的每個參數分別賦值給了__Block_byref_width_0結構體中的每個成員變量
  • 而在block內部從新對width從新賦值的邏輯中
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_width_0 *width = __cself->width; // bound by ref

    (width->__forwarding->width) = 12;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_9241d5_mi_0, (width->__forwarding->width));
}
複製代碼
  • 上面代碼中的width是一個__Block_byref_width_0類型的變量
  • width對象經過找到內部的__forwarding成員變量
  • __Block_byref_width_0結構體中__forwarding是一個指向本身自己的成員變量
  • 因此最後再經過__forwarding找到__Block_byref_width_0的成員變量width, 在進行從新賦值
  • NSLog中也是經過這種邏輯獲取width的值

歡迎您掃一掃下面的微信公衆號,訂閱個人博客!

微信公衆號
相關文章
相關標籤/搜索