探尋Block的本質(3)—— 基礎類型的變量捕獲

Block傳送門🦋🦋🦋

探尋Block的本質(1)—— 基本認識c++

探尋Block的本質(2)—— 底層結構面試

探尋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++文件block中的函數帶參數很明顯,參數a、b也被封裝進了block中,這種狀況也比較簡單,過一下就好。ui

Block捕獲auto變量

接下來看看這種狀況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中打印出的a10,咱們在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對於這幾種變量的使用,作了如何的處理。

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賦值過程對於static變量,block捕獲的是它的地址很清晰,確實block構造函數裏面傳入的,就是外部的這個height的地址值。

(3)最後看block內部的函數那麼能夠看到,block內部的函數也是經過block所存儲的地址值*height訪問了外部的static變量height的值。

所以,當咱們從外部修改height的值以後,調用block打印出的height的值也相應的改變了,由於block內部是經過 指針 引用了外部的這個static變量height

❓思考❓對於autostatic變量,爲何block選擇用不一樣方式處理它們呢?

咱們知道,一個自動變量auto)的存儲空間位於函數棧空間上,在函數開闢棧空間時被建立,在函數結束時銷燬,而block的調用時機有可能發生在函數結束以後的,所以就沒法使用自動變量了,因此在block一開始定義賦值的過程裏,就將自動變量的值拷貝到他本身的存儲空間上。

而對於局部靜態變量(static),C語法下static會改變所修飾的局部變量的生命週期,使其在 程序整個運行期間都存在 ,因此block選擇持有它的指針,在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沒有對全局變量進行捕獲行爲,只須要在要用的時候,直接經過變量名訪問就好了,由於全局變量時跨函數的,能夠直接經過變量的名字直接訪問。 一樣,者也幫我咱們理解了爲何對於局部的變量,block須要對其採起「捕獲」行爲,正是由於局部變量定在與函數內部,沒法跨函數使用,因此根據局部變量不一樣的存儲屬性,要麼將其值直接進行拷貝(auto),要麼對其地址進行拷貝(static)。

總結

  1. 局部變量會被block捕獲
  • 自動變量(auto),block經過值拷貝方式捕獲,在其內部建立一個同類型變量,而且將自動變量的值拷貝給block的內部變量,block代碼塊執行的時候,直接訪問它的這個內部變量
  • 靜態變量(static),block經過地址拷貝方式捕獲,在其內部建立一個指向同類型變量的指針, 將靜態變量的地址值拷貝給block內部的這個指針,block代碼塊執行的時候,經過內部存儲的指針間接訪問靜態變量
  1. 全局變量不會被block捕獲, block代碼塊執行的時候,經過全局變量名直接訪問

Block對於self的處理

請問上圖block裏面的self會被該block捕獲嗎? 編譯結果顯示blockself進行了捕獲。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的本質(4)—— Block的類型

探尋Block的本質(5)—— 對象類型的變量捕獲

探尋Block的本質(6)—— __block的深刻分析

相關文章
相關標籤/搜索