探尋Block的本質(4)—— Block的類型markdown
探尋Block的本質(5)—— 對象類型的變量捕獲iphone
探尋Block的本質(6)—— __block的深刻分析函數
上一篇裏面,咱們分析了一下 胚胎版的Block 的底層結構。如今咱們加點料進去post
#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中,這種狀況也比較簡單,過一下就好。ui
接下來看看這種狀況spa
#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中打印出的a
是10
,咱們在block外部對age
的修改結果並無對block的內部打印產生影響,爲何呢?咱們一樣,藉助編譯後的c++文件來看一看。3d
(1)首先看一下此時block對應的結構體
咱們發現有三處變化
- 新增了一個
int age
成員變量- 構造函數裏面多了一個參數
int _age
- 構造函數裏面參數尾部多了一個
: age(_age)
,這是c++的語法,做用時將參數_age
自動賦值給成員變量age
(2)而後在看一下main函數中的block定義以及賦值的代碼
在用
block
構造函數生成block
的時候,使用了外部定義的int a = 10
,由於c函數的參數都是值傳遞,因此這裏是將此時外部變量a
的值10
傳給了block
的構造函數__main_block_impl_0
,所以block內部的成員變量age
會被賦值成10
。
(3)再看一下block內部封裝的函數
能夠看到打印代碼裏面使用的
age
,實際上就是block內部的成員變量age
,不是咱們在外面定義的那個age
,所以,當block被賦值以後,其成員變量age
被賦值成了當時構造函數傳進來的參數10
,因此最終打印出來值就是10
,不論外部的age
再如何的修改。外部的age
跟block的成員變量age
是兩個不一樣的變量,互不影響。
其實,上面我門討論的這個block外部變量
age
是一個局部auto變量,也叫自動變量,這是C語言的知識點,若是有不清楚的請自行補腦。咱們知道除了auto變量
,C語言裏面還有局部static變量
(靜態變量)和全局變量,接下來咱們就看看,Block對於這幾種變量的使用,作了如何的處理。
首先咱們將上面的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
)的存儲空間位於函數棧空間上,在函數開闢棧空間時被建立,在函數結束時銷燬,而block的調用時機有可能發生在函數結束以後的,所以就沒法使用自動變量了,因此在block一開始定義賦值的過程裏,就將自動變量的值拷貝到他本身的存儲空間上。
而對於局部靜態變量(static
),C語法下static
會改變所修飾的局部變量的生命週期,使其在 程序整個運行期間都存在 ,因此block選擇持有它的指針,在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捕獲
- 自動變量(
auto
),block經過值拷貝方式捕獲,在其內部建立一個同類型變量,而且將自動變量的值拷貝給block的內部變量,block代碼塊執行的時候,直接訪問它的這個內部變量。- 靜態變量(
static
),block經過地址拷貝方式捕獲,在其內部建立一個指向同類型變量的指針, 將靜態變量的地址值拷貝給block內部的這個指針,block代碼塊執行的時候,經過內部存儲的指針間接訪問靜態變量。
- 全局變量不會被block捕獲, block代碼塊執行的時候,經過全局變量名直接訪問。
請問上圖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對於基礎類型環境變量的處理以及調用過程,就整理完畢了。