Block中是如何實現截獲自動變數值的呢?

2021-09-11 20:34:04 字數 4837 閱讀 3640

我們都說block會捕獲(持有)它使用到的區域性變數的值,可是它是如何實現捕獲自動變數的值的呢?

下面依然是使用一段**,然後用clang進行轉換,來分析其過程。

轉換前的main.m原始碼:

#import

intmain

(int argc,

const

char

* ar**)

; a =20;

blk();

}return0;

}

struct __main_block_impl_0 };

static

void

__main_block_func_0

(struct __main_block_impl_0 *__cself)

static

struct __main_block_desc_0 __main_block_desc_0_data =

;int

main

(int argc,

const

char

* ar**)

return0;

}

由此推斷,在__main_block_impl_0中會多出其使用到的變數儲存位置,然後在其建構函式中,將使用到的區域性變數值儲存到結構體例項中。

__main_block_func_0的實現過程,也發生了一些變化。

在使用區域性變數時,是先將其從__main_block_impl_0例項的例項變數中取出,然後再使用這個自己儲存的值,所以不管外部如何修改,都不影響內部的這個值。

簡單總結下來,就是block中使用到的區域性變數,都會在編譯時動態建立的block實現結構體中建立乙個與區域性變數名稱一樣的例項變數,該例項變數儲存著外部的區域性變數的值,而當執行block時,再將這裡儲存下來的值取出來,所以這外部和block內部使用的是兩個不同的變數,因此即使外部先修改了外部變數的值,再執行block,也不會影響到block內的例項變數的值。

轉換前main.m中的測試**:

#import

intmain

(int argc,

const

char

* ar**)

; a =20;

blk();

}return

0;

轉換後的main.cpp中的block**:

struct __main_block_impl_0 };

static

void

__main_block_func_0

(struct __main_block_impl_0 *__cself)

static

struct __main_block_desc_0 __main_block_desc_0_data =

;int

main

(int argc,

const

char

* ar**)

return0;

}

從轉換後的**,可以看出與推斷的一致。

原始**:

#import

intmain

(int argc,

const

char

* ar**)

; name =

@"lisi"

; a =20;

blk();

}return0;

}

轉換後的**:

struct __main_block_impl_0 };

static

void

__main_block_func_0

(struct __main_block_impl_0 *__cself)

static

void

__main_block_copy_0

(struct __main_block_impl_0*dst,

struct __main_block_impl_0*src)

static

void

__main_block_dispose_0

(struct __main_block_impl_0*src)

static

struct __main_block_desc_0 __main_block_desc_0_data =

;int

main

(int argc,

const

char

* ar**)

return0;

}

從轉換後的原始碼,可以看出block中使用的物件型別,依然會在__main_block_impl_0中新增乙個同樣型別的例項變數。然後依然是建立__main_block_impl_0變數時,將物件對應的指標賦值給內部例項變數。後面變數時,也是用內部例項變數。

但是捕獲物件型別時,__main_block_desc_0的結構體會有一些變化,多了兩個函式。這兩個函式分別是用來拷貝變數和釋放變數的。這個再後面篇章再介紹。

通過上面的轉換後的**,我們已經知道了,在block執行函式中,使用的是__main_block_impl_0中儲存的例項變數。所以,如果我們修改這個例項變數的值,修改的也是__main_block_impl_0中儲存的例項變數的值,無法修改到外部的區域性變數,而實際上,我們在block中是想修改外部的區域性變數的值,所以針對這種情況編譯器就給我們報錯,來提醒我們:

variable is not assignable (missing __block type specifier)
注意:這裡指的是修改沒有__block修飾符的區域性變數。

那有沒有特殊情況呢?

答案是有的,有三種特殊的變數即使不使用__block,也可以在block中修改的:

我們依然使用clang來轉換一下試試看。

測試用的原始**:

#import

static

int static_global_value =1;

int global_value =2;

intmain

(int argc,

const

char

* ar**)

;blk()

;nslog

(@"static_global_value:%d---global_value:%d----static_value:%d"

,static_value);}

return0;

}

然後使用clang命令(clang -rewrite-objc main.m)轉換。

轉換後的block**如下:

static

int static_global_value =1;

int global_value =2;

struct __main_block_impl_0 };

static

void

__main_block_func_0

(struct __main_block_impl_0 *__cself)

static

struct __main_block_desc_0 __main_block_desc_0_data =

;int

main

(int argc,

const

char

* ar**)

return0;

}

從以上**可以看出,block並不會捕獲全域性變數,而函式內的靜態變數,雖然block的結構體內部依然也有乙個變數,但是該變數儲存的確是靜態變數的指標,在執行block時,因為使用的是內部儲存的外部變數的指標,所以使用指標修改後,就等價於外部的靜態變數也被修改了。

那既然可以儲存變數的指標,block中為何儲存區域性變數的值,而不是儲存指標呢?

這跟記憶體區域有關!

我們知道記憶體區域有:全域性區、堆區、棧區、常量區、程式**區。

因為靜態變數和靜態全域性變數是儲存在全域性區,它們是在程式結束後,由系統釋放。所以出了函式作用域外,這些變數依然存在,那麼block中就依然可以正常使用。

但是區域性變數,雖然可以使用變數指標儲存,但是出了函式作用域,這些變數就被釋放了,那麼我們就不能在block中正常訪問了。

因此block中是建立乙個新的例項變數來儲存其捕獲的區域性變數,而不是建立乙個指標來儲存捕獲的區域性變數。

關於記憶體的5大分割槽和作用,簡要記錄一下:

棧區(stack)— 由編譯器自動分配釋放 ,存放函式的引數值,區域性變數的值等。

堆區(heap) — 一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由系統** 。arc下雖然不用我們管,其實是由於編譯器已經幫我們在合適的位置插入了retain/release/autorelease等而已。

全域性區(靜態區)(static)—全域性變數和靜態變數的儲存是放在一塊的。初始化的全域性變數和靜態變數在一塊區域, 未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。程式結束後由系統釋放。

文字常量區 —常量字串/const常量就是放在這裡的。 程式結束後由系統釋放。

程式**區—存放函式體的二進位制**。

詳細的可以看深入淺出-ios記憶體分配與分割槽

Block中 block實現原理

三.block中 block實現原理 我們繼續研究一下 block實現原理。1.普通非物件的變數 先來看看普通變數的情況。import int main int argc,const char ar myblock return 0 把上述 用clang轉換成原始碼。struct block byr...

springboot自動配置是如何實現的?

什麼是springboot自動配置?springboot的自動配置,指的是springboot會自動將一些配置類的bean註冊進ioc容器,我們可以需要的地方使用 autowired或者 resource等註解來使用它。自動 的表現形式就是我們只需要引我們想用功能的包,相關的配置我們完全不用管,sp...

Objc中block的實現

閉包 閉包是乙個函式 或者是指向函式的指標 再加上函式執行上下文的變數 有時候也稱做自由變數 block 實際上就是 oc語言對閉包的實現。block的資料結構定義如下 isaflags 用bit位 表示一些block的附加描述資訊 reserved 保留變數 invoke 函式指標 指向具體的bl...