Block系列文章—————————————
Block筆記(1)—— 基本認識
Block筆記(2)—— 底層結構
Block筆記(3)—— 基礎類型的變量捕獲
Block筆記(4)—— Block的類型
Block筆記(5)—— 對象類型的auto變量捕獲
Block筆記(6)— __block的深入分析
————————————————————
上一章節裡面,我們分析了一下胚胎版的Block的底層結構。現在我們加點料進去
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//Block的定義
void (^block)(int, int) = ^(int a, int b){
NSLog(@"I am a block! - %d - %d", a, b);
};
//Block的調用
block(10, 20);
}
return 0;
}
****************************** 日志輸出 *******************************
2019-06-04 15:30:57.747093+0800 Interview03-block[3915:354992] I am a block! - 10 - 20
Program ended with exit code: 0
這裡我們給block所封裝的函數增加兩個參數a、b,還是慣例,通過指令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
然後打開編譯後的c++檔案
很明顯,參數a、b也被封裝進了block中,這種情況也比較簡單,過一下就好。
Block捕獲auto變量
接下來看看這種情況
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
//Block的定義
void (^block)(void) = ^(){
NSLog(@"Age is %d", age);
};
//先修改age的值
age = 20;
//Block的調用
block();
}
return 0;
}
我在block之前定義了一個
int a = 10
,然後在block内部使用了這個
age
,而且我在調用block之前,先将
age
的值修改成了
20
,那麼此時程式運作會是什麼結果呢?相信以大部分人對block的了解,應該都能給出正确答案。
2019-06-04 15:46:01.244557+0800 Interview03-block[4064:375528] Age is 10
Program ended with exit code: 0
結果是block中列印出的
age
是10,我們在block外部對
age
的修改結果并沒有對block的内部列印産生影響,為什麼呢?我們同樣,借助編譯後的c++檔案來看一看。
(1)首先看一下此時block對應的結構體 我們發現有三處變化
- 新增了一個
成員變量
int age
- 構造函數裡面多了一個參數
int _age
- 構造函數裡面參數尾部多了一個
,這是c++的文法,作用是将參數
: age(_age)
自動指派給成員變量
_age
int age
(2)然後在看一下main函數中的block定義以及指派的代碼 在用構造函數生成
block
的時候,使用了外部定義的
block
,因為c函數的參數都是值傳遞,是以這裡是将此時外部變量
int a = 10
的值
a
傳給了
10
的構造函數
block
,是以block内部的成員變量
__main_block_impl_0
會被指派成
age
。
10
(3)再看一下block内部封裝的函數__main_block_func_0 可以看到列印代碼裡面使用的,實際上就是block内部的成員變量
age
,不是我們在外面定義的那個
age
,是以,當block被指派之後,其成員變量
age
被指派成了當時構造函數傳進來的參數
age
,是以最終列印出來值就是
10
,不論外部的
10
再如何的修改。外部的
age
跟block的成員變量
age
是兩個不同的變量,互不影響。
age
其實,上面我門讨論的這個block外部變量是一個局部變量,也叫自動變量(auto變量),這是C語言的知識點,如果有不清楚的請自行補腦。我們知道除了
age
,C語言裡面還有局部
auto變量
(靜态變量)和全局變量,接下來我們就看看,Block對于這幾種變量的使用,做了如何的處理。
static變量
Block捕獲局部static變量
首先我們将上面的OC代碼改造如下
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
static int height = 10;
//Block的定義
void (^block)(void) = ^(){
NSLog(@"Age is %d, height is %d", age, height);
};
//先修改age和height的值
age = 20;
height = 20;
//Block的調用
block();
}
return 0;
}
我們有增加了一個
static
變量
height
,并且在同樣的地方修改
height
的值,便于和之前的
age
進行對比。首先運作代碼看一下結果
2019-06-04 17:10:12.935220+0800 Interview03-block[4725:476530] Age is 10, height is 20
Program ended with exit code: 0
可以看到,block輸出的
height
值是我們在外部重新為其賦的
20
。
為什麼呢,我們進入編譯後的C++檔案一看究竟
(1)借用上面的分析流程一樣,先看一下block對應的結構體 你看,針對static變量height, block内部為其增加了一個成員變量,構造函數裡面對應的參數是
int *height;
。看到這裡,應該就大緻能猜出來,我們這裡要存儲的是一個位址,該位址應該就是外部
int *_height
變量
static
的位址值。
height
(2)那我們來看一下main函數裡的block指派過程 很清晰,确實block構造函數裡面傳入的,就是外部的這個height的位址值。
(3)最後看block内部的函數 那麼可以看到,block内部的函數也是通過block所存儲的位址值通路了外部的
*height
變量
static
的值。
height
是以,當我們從外部修改
height
的值之後,調用block列印出的
height
的值也相應的改變了,因為block内部是通過 指針 引用了外部的這個
static
變量
height
。
❓思考❓對于 auto
、 static
變量,為什麼block選擇用不同方式處理它們呢?
auto
static
我們知道,一個自動變量(
auto
)的存儲空間位于函數棧空間上,在函數開辟棧空間時被建立,在函數結束時銷毀,而block的調用時機有可能發生在函數結束之後的,是以就無法使用自動變量了,是以在block一開始定義指派的過程裡,就将自動變量的值拷貝到他自己的存儲空間上。
而對于局部靜态變量(
),C文法下
static
會改變所修飾的局部變量的生命周期,使其在 程式整個運作期間都存在 ,是以block選擇持有它的指針,在block被調用時,通過該指針通路這個變量的内容就行。
static
Block使用全局變量
上面我們讨論block對于局部變量的處理,我們在看一看對于全局變量,情況又是如何
輸出結果如下
2019-06-05 09:19:08.854599+0800 Interview03-block[13997:1263406] Age is 20, height is 20
Program ended with exit code: 0
在通過指令行生成一下編譯後的C++檔案,同樣還是在檔案底部去看
這次就非常痛快了,block沒有對全局變量進行捕獲行為,隻需要在要用的時候,直接通過變量名通路就行了,因為全局變量是跨函數的,是以可以直接通過變量的名字直接通路。
同樣,這也幫我我們了解了為什麼對于局部的變量,block需要對其采取“捕獲”行為,正是因為局部變量定在與函數内部,無法跨函數使用,是以根據局部變量不同的存儲屬性,要麼将其值直接進行拷貝(
auto
),要麼對其位址進行拷貝(
static
)。
總結
- 局部變量會被block捕獲
- 自動變量(
),block通過值拷貝方式捕獲,在其内部建立一個同類型變量,并且将自動變量的值拷貝給block的内部變量,block代碼塊執行的時候,直接通路它的這個 内部變量。
auto
- 靜态變量(
),block通過位址拷貝方式捕獲,在其内部建立一個指向同類型變量的指針, 将靜态變量的位址值拷貝給block内部的這個指針,block代碼塊執行的時候,通過内部存儲的指針 間接通路 靜态變量。
static
- 全局變量不會被block捕獲, block代碼塊執行的時候,通過全局變量名 直接通路。
Block對于self的處理
請問上圖block裡面的
self
會被該block捕獲嗎?
編譯結果顯示block對
self
進行了捕獲。But why?
我們知道,圖中的block位于
test
方法裡面,實際上任何的oc方法,轉換成底層的c函數,裡面都有兩個預設的參數,
self
和
_cmd
,是以作為函數預設參數的
self
的實際上也是該函數的局部變量,根據我們上面總結的原則,隻要是局部變量,block都會對其進行捕獲,這就解釋通了。
那麼有人會問(特别是面試官)下面的情況呢
先看編譯結果
看得出來,還是進行了捕獲,看我在圖中标明的黃色框框,就很好了解了,block最終通路
CLPerson
的成員變量
_age
的時候,是通過
self
+
_age偏移量
,獲得
_age
的位址後進而進行間接通路的,是以在oc代碼中,
_age
的寫法等同與
self->_age
,說白了,這裡還是需要用到
self
,是以block還是需要對
self
進行捕獲的。
至此,有關Block對于基礎類型環境變量的處理以及調用過程,就整理完畢了。
Block系列文章—————————————
Block筆記(1)—— 基本認識
Block筆記(2)—— 底層結構
Block筆記(3)—— 基礎類型的變量捕獲
Block筆記(4)—— Block的類型
Block筆記(5)—— 對象類型的auto變量捕獲
Block筆記(6)— __block的深入分析