block底層實現與變數捕獲

2021-10-08 23:20:37 字數 4858 閱讀 7241

本文已經新增到**:《徹底弄懂oc》。 歡迎加入我的qq群:661461410,一起**ios底層原理。

block的本質是什麼?你能講出來它的底層結構嗎?

全域性變數會被block捕獲嗎?block會捕獲哪些變數?

block又叫**塊,是oc語法中非常重要的乙個概念,我們先來看一下block的簡單使用。

int main(int argc, const char * ar**) ();

int d = 5;

void (^block)(int, int) = ^(int a, int b) ;

block(3, 4);

}return 0;

}

上面的**中,我們建立了兩個block,乙個直接執行,輸出hello world。 乙個通過block變數進行呼叫,並引用了乙個外部變數d。輸出12

我們將以上**編譯成c**:

# 在main.m所在目錄執行該命令。

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

從main-arm64.cpp檔案中,我們可以看到block的結構如下:

struct __main_block_impl_1 

};struct __block_impl ;

我們可以看出block的底層是結構體,__main_block_impl_1包含乙個變數impl其結構和class的結構類似,其包含乙個isa指標,可見block本質上也是乙個類,其中funcptr表示要執行的**塊的函式位址。d表示它引用的外部變數。

下面,我們一起看一下block的呼叫過程,首先我們將下面**,編譯成c**。

int main(int argc, const char * ar**) ;

block();

}return 0;

}// 下面是編譯後的c**

int main(int argc, const char * ar**)

return 0;

}static void __main_block_func_0(struct __main_block_impl_0 *__cself)

static struct __main_block_desc_0 __main_block_desc_0_data = ;

針對,上面的兩行**,先呼叫__main_block_impl_0的結構體建構函式,建立block,並將位址賦值給我block。而__main_block_func_0是對應block內部要執行的**,是乙個靜態的方法,它會賦值給__block_impl中的funcptr

__main_block_desc_0_data是也是乙個結構體變數,裡面的兩個引數reserved為 0,block_size__main_block_impl_0結構體的大小。

呼叫的時候,是從block裡面直接取出funcptr。 我們知道block__main_block_impl_0型別,由於結構體的特性,將block強轉為__block_impl型別,是可以直接取到funcptr的。所以第二句的呼叫也是清晰的。

上面的兩句**去掉強制型別轉化,可以精簡為:

void

(*block)

(void)=

&__main_block_impl_0

(__main_block_func_0,

&__main_block_desc_0_data));

block->

funcptr

(block)

;

這樣,呼叫過程就清晰多了。

通過上面的分析,我們可以看出block的結構應該是如下圖所示:

auto自動變數是離開作用域,就會銷毀, 只存在區域性變數裡面,不能修飾全域性變數。

比如,下面例子中的ageweight就是auto變數,他們離開自己所在的作用局就會銷毀。預設情況下auto關鍵字會自動新增。

int main(int argc, const char * ar**) 

// 在這裡訪問age, weight就報錯了。

}return 0;

}

如果block中使用了auto變數,那麼block就會捕獲該變數,下面**

int main(int argc, const char * ar**) ;

age = 40;

block();}}

return 0;

}

列印的結果中 age為20 還是 40? 編譯後,__main_block_impl_0的結構如下,增加了兩個int 變數。

struct __main_block_impl_0 

};

我們可以看出,捕獲了auto變數,而且是值傳遞。

static變數

下面**輸入結果是什麼?

int main(int argc, const char * ar**) ;

height = 80;

block();}}

return 0;

}

結果是80,為什麼呢? 我們依然通過編譯後的結果檢視。

struct __main_block_impl_0 };

intmain

(int argc,

const

char

* ar**)

}return0;

}static

void

__main_block_func_0

(struct __main_block_impl_0 *__cself)

我們可以看出__main_block_impl_0中增加了乙個變數height,但需要注意的是它是int *型別的,在給它賦值的時候傳入的是&height。 在__main_block_func_0中訪問的時候是通過*height取值的。

因此我們可以得出結論,靜態變數也是會被block捕獲的,但它捕獲的是指標。

全域性變數

下面**,輸出的結果是什麼?

int age = 10;

int main(int argc, const char * ar**) ;

age = 20;

block();}}

return 0;

}

輸入結果是20,那block捕獲了age嗎?是通過指標訪問的嗎?我們看一下編譯結果:

struct __main_block_impl_0 

};

可以看出block並沒有捕獲全域性變數。

結論通過上面的分析,我們可以得出結論:

下面**中,person類中的test方法中block捕獲了變數了嗎?捕獲了那個變數?

// main.m

#import #import "person.h"

int main(int argc, const char * ar**)

return 0;

}// person.h

@inte***ce person : nsobject

@property (nonatomic, copy)nsstring *name;

- (void)test;

@end

// person.m

@implementation person

- (void)test ;

block();

}@end

用上面的命令將person.m編譯c++**,如下:

struct __person__test_block_impl_0 

};

可以看出,這裡捕獲的並不是name,而是person物件,這涉及了block的迴圈引用,我們將在下面的文章中講述。

下面各個**的輸出結果是什麼?

//問題1

int main(int argc, const char * ar**) ;

array[0] = @"dgf";

block();

}return 0;

}//問題2

int main(int argc, const char * ar**) ;

array = [@[@"dgf"] mutablecopy];

block();

}return 0;

}

Block底層實現

步驟一 建立乙個控制台應用,在main中新增塊的測試 import int main int argc,const char ar testblock return 0 步驟二 開啟終端,定位到main.m檔案目錄,錄入xcode命令 xcrun sdk iphoneos clang arch ar...

block的底層實現原理

block就是指向結構體的指標,編譯器會將block的內部 生成對應的函式,利用這個指標就可以呼叫這個函式.普通的區域性變數是值傳遞,用 block static 或者是全域性變數就是位址傳遞 block的記憶體預設是存放在棧裡面的,他不會對所引用的物件進行操作 如果對block做一次copy操作b...

OC底層 block內修改變數

講一下以上兩個問題 對於捕獲問題,只有區域性變數才能 獲,全域性變數不需要捕獲,直接拿著使用,但是有一點,當block在棧中時,block是不能捕獲物件型變數,直接拿著使用,只有在堆中的block才能捕獲物件型變數 對於block內部修改問題,因為變數捕獲有兩種,一種是值捕獲,一種是位址捕獲,對於這...