Block
簡介block
是將函數及其執行上下文封裝起來的一個對象block
實現的內部,有不少變量,由於block
也是一個對象isa
指針,imp
指針等對象變量,還有儲存其截獲變量的對象等block
根據有無參數和有無返回值有如下幾種簡單使用方式html
// 無參數無返回值
void (^ BlockOne)(void) = ^(void){
NSLog(@"無參數,無返回值");
};
BlockOne();//block的調用
// 有參數無返回值
void (^BlockTwo)(int a) = ^(int a){
NSLog(@"有參數,無返回值, 參數 = %d,",a);
};
BlockTwo(100);
// 有參數有返回值
int (^BlockThree)(int,int) = ^(int a,int b){
NSLog(@"有參數,有返回值");
return a + b;
};
BlockThree(1, 5);
// 無參數有返回值
int(^BlockFour)(void) = ^{
NSLog(@"無參數,有返回值");
return 100;
};
BlockFour();
複製代碼
但是以上四種block
底層又是如何實現的呢? 其本質到底如何? 接下來咱們一塊兒探討一下c++
Command Line Tool
項目, 在main
函數中執行上述中一個block
Block
的本質, 就要查看其源碼, 這裏咱們使用下面命令把main.m
文件生成與其對應的c++
代碼文件main.m
文件所在的目錄下, 執行以下命令, 會生成一個main.cpp
文件main.cpp
文件添加到項目中, 並使其不參與項目的編譯, 下面咱們就具體看一下block
的底層究竟是如何實現的xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
複製代碼
打開main.cpp
文件, 找到文件最底部, 能夠看到block
的相關源碼以下程序員
// block的結構體
struct __main_block_impl_0 {
// 結構體的成員變量
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 構造函數
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 封裝了block執行邏輯的函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_11c959_mi_0);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 定義block變量
void (* BlockOne)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 執行block內部的源碼
((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
複製代碼
其中block
的聲明和調用的對應關係以下bash
刪除其中的強制轉換的相關代碼後微信
// 定義block變量
void (* BlockOne)(void) = &__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA
);
// 執行block內部的源碼
BlockOne->FuncPtr(BlockOne);
複製代碼
上述代碼中__main_block_impl_0
函數接受兩個參數, 並有一個返回值, 最後把函數的地址返回給BlockOne
, 下面找到__main_block_impl_0
的定義iphone
// 結構體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// c++中的構造函數, 相似於OC中的init方法
// flags: 默認參數, 調用時可不傳
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
__main_block_impl_0
函數中的第一個參數__main_block_func_0
賦值給了fp
, fp
又賦值給了impl.FuncPtr
, 也就意味着impl.FuncPtr
中存儲的就是咱們要執行的__main_block_func_0
函數的地址Block
結構體中的isa
指向了_NSConcreteStackBlock
, 說明Block
是一個_NSConcreteStackBlock
類型, 具體後面會詳解__main_block_impl_0
函數中的第二個參數__main_block_desc_0_DATA
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
複製代碼
reserved
賦值爲0Block_size
被賦值爲sizeof(struct __main_block_impl_0)
, 即爲__main_block_impl_0
這個結構體佔用內存的大小__main_block_impl_0
的第二個參數, 接受的即爲__main_block_desc_0
結構體的變量(__main_block_desc_0_DATA
)的地址auto
和static
auto
: 自動變量, 離開做用域就會自動銷燬, 默認狀況下定義的局部變量都是auto
修飾的變量, 系統都會默認給添加一個auto
auto
不能修飾全局變量, 會報錯static
做用域內修飾局部變量, 能夠修飾全局變量
auto
局部變量在Block
中是值傳遞函數
下述代碼輸出值爲多少?ui
int age = 10;
void (^BlockTwo)(void) = ^(void){
NSLog(@"age = %d,",age);
};
age = 13;
BlockTwo();
// 輸出10
複製代碼
輸出值爲何是10而不是13呢? 咱們仍是生成main.cpp
代碼看一下吧, 相關核心代碼以下spa
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 這裏多了一個age屬性
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_80d62b_mi_0,age);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 定義屬性
int age = 10;
// block的定義
void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
// 改變屬性值
age = 13;
// 調用block
((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
複製代碼
那麼下面咱們一步步看一下, 吧一些強制轉換的代碼去掉以後指針
int age = 10;
void (*BlockTwo)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
age
);
age = 13;
BlockTwo->FuncPtr(BlockTwo);
複製代碼
在上面的__main_block_impl_0
函數裏面相比於以前的, 多了一個age
參數
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 新的屬性age
int age;
// 構造函數, 多了_age參數
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
__main_block_impl_0
中, 多了一個_age
參數age(_age)
語句, 在c++
中, age(_age)
至關於age = _age
, 即給age
屬性賦值, 存儲構造函數傳過來的age
屬性的值block
的時候, block
對應的結構體所存儲的age
屬性的值仍然是10, 並無被更新// 及時這裏從新對age進行了賦值
age = 13;
// 這裏調用BlockTwo的時候, 結構體重的age屬性的值並無被更新
BlockTwo->FuncPtr(BlockTwo);
// 最後在執行block內部邏輯的時候,
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
// 這裏的age, 仍然是block結構體中的age, 值並無改變, 因此輸出結果仍是10
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_80d62b_mi_0,age);
}
複製代碼
static
局部變量在Block
中是指針傳遞, 看一下下面代碼的輸出狀況
auto int age = 10;
static int weight = 20;
void (^BlockTwo)(void) = ^(void){
NSLog(@"age = %d, weight = %d,",age, weight);
};
age = 13;
weight = 23;
BlockTwo();
複製代碼
age = 10, weight = 23
age
的結果不變, 以前已經說過了weight
的結果倒是賦值後的結果, 至於爲何, 請繼續向下看吧...main.cpp
代碼看一下吧, 相關核心代碼以下struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *weight;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *weight = __cself->weight; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_282a93_mi_0,age, (*weight));
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
auto int age = 10;
static int weight = 20;
void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &weight));
age = 13;
weight = 23;
((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
複製代碼
__main_block_impl_0
類中多了兩個成員變量age
和weight
, 說明兩個變量咱們均可以捕獲到int
變量, 使用不一樣的修飾詞修飾, __main_block_impl_0
類中也是不一樣的static
修飾的變量weight
在block
中存儲的是weight
的地址, 在後面的block
函數中咱們使用的也是其地址int age;
int *weight;
// &weight
void (*BlockTwo)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age, &weight);
// 下面構造方法中, 一樣(weight(_weight)方法以前講過)將傳過來的weight的地址賦值給了 (int *weight;)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_weight, int flags=0) : age(_age), weight(_weight) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
複製代碼
age
保存的是一個準確的值weight
保存的是weight
所在的內存地址block
內部邏輯的時候static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *weight = __cself->weight; // bound by copy
// (*weight)至關於從weight的內存地址中取值, 在執行操做
// 然而weight內存中的值已經在後面賦值的時候被更新了, 因此這裏取出的值是賦值後的
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_282a93_mi_0,age, (*weight));
}
複製代碼
auto
修飾的變量在block
中存儲的是變量的值(值傳遞)static
修飾的變量在block
中存儲的是變量的內存地址(地址傳遞)int age = 10;
static int weight = 20;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^BlockTwo)(void) = ^(void){
NSLog(@"age = %d, weight = %d,",age, weight);
};
age = 13;
weight = 23;
BlockTwo();
}
return 0;
}
複製代碼
上面代碼的輸出結果, 毫無疑問是13和23, 相關c++
代碼以下
int age = 10;
static int weight = 20;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 封裝了block執行邏輯的函數
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_0ee0bb_mi_0,age, weight);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 定義block變量
void (*BlockTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
age = 13;
weight = 23;
((void (*)(__block_impl *))((__block_impl *)BlockTwo)->FuncPtr)((__block_impl *)BlockTwo);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
複製代碼
__main_block_impl_0
結構體重並無捕獲到age
和weight
的成員變量block
變量的時候中也不須要傳入age
和weight
的變量block
執行邏輯的函數中, 就能夠直接使用全局的變量便可C++
源碼中, __main_block_impl_0
結構體中isa
指向的類型是_NSConcreteStackBlock
Block
的只要類型有那些void (^block)(void) = ^(void){
NSLog(@"Hello World");
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
/*
2019-06-24 15:46:32.506386+0800 Block[3307:499032] __NSGlobalBlock__
2019-06-24 15:46:32.506578+0800 Block[3307:499032] __NSGlobalBlock
2019-06-24 15:46:32.506593+0800 Block[3307:499032] NSBlock
2019-06-24 15:46:32.506605+0800 Block[3307:499032] NSObject
*/
複製代碼
block
的類型NSBlock
最終也是繼承自NSObject
block
的結構體__main_block_impl_0
中會有一個isa
指針了block
共有三種類型, 能夠經過調用class
方法或者isa
指針查看具體類型, 最終都是繼承自NSBlock
類型
__NSGlobalBlock__
或者_NSConcreteGlobalBlock
__NSStackBlock__
或者_NSConcreteStackBlock
__NSMallocBlock__
或者_NSConcreteMallocBlock
_NSConcreteGlobalBlock
: 在數據區域_NSConcreteStackBlock
: 在棧區域_NSConcreteMallocBlock
: 在堆區域alloc
或者malloc
方式申請的內存block
類型, 且不一樣的block
類型存放在內存的不一樣位置block
究竟是哪種類型呢 看看下面代碼的執行狀況, 運行環境實在MRC
環境下static int age = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int weight = 21;
void (^block1)(void) = ^(void){
NSLog(@"Hello World");
};
void (^block2)(void) = ^(void){
NSLog(@"age = %d", age);
};
void (^block3)(void) = ^(void){
NSLog(@"age = %d", weight);
};
NSLog(@"block1 = %@", [block1 class]);
NSLog(@"block2 = %@", [block2 class]);
NSLog(@"block3 = %@", [block3 class]);
/* 2019-06-24 21:13:14.555206+0800 Block[30548:1189724] block1 = __NSGlobalBlock__ 2019-06-24 21:13:14.555444+0800 Block[30548:1189724] block2 = __NSGlobalBlock__ 2019-06-24 21:13:14.555465+0800 Block[30548:1189724] block3 = __NSStackBlock__ */
}
return 0;
}
複製代碼
針對各類不一樣的block
總結以下
block 類型 |
環境 |
---|---|
__NSGlobalBlock__ |
沒有訪問auto變量 |
__NSStackBlock__ |
訪問了auto變量 |
__NSMallocBlock__ |
__NSStackBlock__ 調用了copy |
__NSMallocBlock__
是放在堆區域__NSMallocBlock__
類型的block
, 咱們能夠調用copy
方法void (^block3)(void) = ^(void){
NSLog(@"age = %d", weight);
};
NSLog(@"block3 = %@", [block3 class]);
NSLog(@"block3 = %@", [[block3 copy] class]);
/* 輸出分別是: block3 = __NSStackBlock__ block3 = __NSMallocBlock__ */
複製代碼
__NSStackBlock__
類型的block
調用copy
方法後, 就會變成__NSMallocBlock__
類型的block
block
是在堆區域的copy
方法後,又會如何? 下面一塊兒來看一下吧int weight = 21;
void (^block1)(void) = ^(void){
NSLog(@"Hello World");
};
void (^block3)(void) = ^(void){
NSLog(@"age = %d", weight);
};
NSLog(@"block1 = %@", [block1 class]);
NSLog(@"block1 = %@", [[block1 copy] class]);
NSLog(@"block3 = %@", [block3 class]);
NSLog(@"block3 = %@", [[block3 copy] class]);
NSLog(@"block3 = %@", [[[block3 copy] copy] class]);
/* __NSGlobalBlock__ __NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__ __NSMallocBlock__ */
複製代碼
__NSStackBlock__
類型的block
調用copy
以後纔會變成__NSMallocBlock__
類型, 其餘的都是原類型__NSStackBlock__
類型的做用域是在棧中, 做用域中的局部變量會在函數結束時自動銷燬__NSStackBlock__
調用copy
操做後,分配的內存地址至關於從棧複製到堆;副本存儲位置是堆Block類 | 副本源的配置存儲域 | 複製效果 |
---|---|---|
__NSStackBlock__ |
棧 | 從棧複製到堆 |
__NSGlobalBlock__ |
程序的數據區域 | 什麼也不作 |
__NSMallocBlock__ |
堆 | 引用計數增長 |
ARC
環境下, 編譯器會根據狀況自動將站上的block
複製到堆上, 相似如下狀況
block
做爲函數返回值時block
賦值給__strong
修飾的指針時block
做爲GCD
的方法參數時Question: 定義一個auto修飾的局部變量, 並在block
中修改該變量的值, 可否修改爲功呢?
auto int width = 10;
static int height = 20;
void (^block)(void) = ^(void){
// 事實證實, 在Xcode中這行代碼是報錯的
width = 22;
// 可是static修飾的變量, 倒是能夠賦值, 不會報錯
height = 22;
NSLog(@"width = %d, height = %d", width, height);
};
block();
// width = 10, height = 22
複製代碼
block
中, auto
修飾的變量是值傳遞static
修飾的變量是指針傳遞, 因此在上述代碼中, block
存儲的只是height
的內存地址auto
變量實在main
函數中定義的, 而block
的執行邏輯是在__main_block_func_0
結構體的方法中執行的, 至關於局部變量不能跨函數訪問static
修飾的變量爲何能夠修改?
__main_block_impl_0
結構體中height
存儲的是其內存地址, 在其餘函數或者結構體中訪問和改變height
的方式都是經過其真真訪問的(*height) = 22;
(*height)
__block auto int width = 10;
void (^block)(void) = ^(void) {
// 很明顯, 這裏就能夠修改了
width = 12;
NSLog(@"width = %d", width);
};
block();
// width = 12
複製代碼
爲何上面的代碼就能夠修改變量了呢, 這是爲何呢.....請看源碼
下面是生成的block
的結構體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 這裏的width被包裝成了一個__Block_byref_width_0對象
__Block_byref_width_0 *width; // by ref
// 這裏能夠對比一下以前的未被__block修飾的int變量
// int width;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_width_0 *_width, int flags=0) : width(_width->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
__block
能夠用於解決block
內部沒法修改auto
修飾的變量值得問題__block
不能修飾全局變量和static
修飾的靜態變量(一樣也不須要, 由於在block
內部能夠直接修改)__block
修飾的變量會被包裝成一個對象(__Block_byref_width_0
)width
被包裝後的對象的結構體, 在結構體內, 會有一個width
成員變量struct __Block_byref_width_0 {
void *__isa;
// 一個指向本身自己的成員變量
__Block_byref_width_0 *__forwarding;
int __flags;
int __size;
// 外部定義的auto變量
int width;
};
複製代碼
下面咱們先看一下, auto
和block
的定義和調用
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// __block auto int width = 10;
auto __Block_byref_width_0 width = {
0,
&width,
0,
sizeof(__Block_byref_width_0),
10
};
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
&width,
570425344
);
block->FuncPtr(block);
}
return 0;
}
複製代碼
__Block_byref_width_0
類型的width
中的每個參數分別賦值給了__Block_byref_width_0
結構體中的每個成員變量block
內部從新對width
從新賦值的邏輯中static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_width_0 *width = __cself->width; // bound by ref
(width->__forwarding->width) = 12;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ty_804897ld2zg4pfcgx2p4wqh80000gn_T_main_9241d5_mi_0, (width->__forwarding->width));
}
複製代碼
width
是一個__Block_byref_width_0
類型的變量width
對象經過找到內部的__forwarding
成員變量__Block_byref_width_0
結構體中__forwarding
是一個指向本身自己的成員變量__forwarding
找到__Block_byref_width_0
的成員變量width
, 在進行從新賦值NSLog
中也是經過這種邏輯獲取width
的值歡迎您掃一掃下面的微信公衆號,訂閱個人博客!