筆者前幾天做了一道筆試題:默認情況下 block 是不能修改外面的 auto 變數的,解決辦法? 思索了好久才想起可以用 __block 或者 static 修飾符來修飾 auto 變數,看來有必要好好探索一下 block 的相關知識,
目錄
1. block 的使用
1.1 什么是 block ?
block 的宣告
block 的定義
block 的呼叫
2. block 的底層資料結構
3. block 的變數捕獲機制
3.1 auto 自動變數
3.2 static 型別的區域變數
3.3 全域變數
為什么區域變數需要捕獲,而全域變數不用呢?
3.4 __block 修飾的變數
3.4.1 __block 的使用:
3.4.2 __block 修飾符
???????
1. block 的使用
1.1 什么是 block ?
塊,封裝了函式呼叫以及呼叫環境的 OC 物件,
-
block 的宣告
// 1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:型別別名
typedef void(^BlockType)(void);
@property (nonatomic, copy) BlockType myBlock2;
// 3.
// 回傳值型別(^block變數名)(引數1型別,引數2型別,...)
void(^block)(void);
-
block 的定義
// ^回傳值型別(引數1,引數2,...){};
// 1.無回傳值,無引數
void(^block1)(void) = ^{
};
// 2.無回傳值,有引數
void(^block2)(int) = ^(int a){
};
// 3.有回傳值,無引數(不管有沒有回傳值,定義的回傳值型別都可以省略)
int(^block3)(void) = ^int{
return 3;
};
// 以上Block的定義也可以這樣寫:
int(^block4)(void) = ^{
return 3;
};
// 4.有回傳值,有引數
int(^block5)(int) = ^int(int a){
return 3 * a;
};
-
block 的呼叫
// 1.無回傳值,無引數
block1();
// 2.有回傳值,有引數
int a = block5(2);
2. block 的底層資料結構
通過 Clang 將以下的 OC 代碼轉換為 C++代碼
// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
//main.m
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"我是 block");
};
block();
}
return 0;
}
轉換為 C++ 代碼 :
struct __main_block_impl_0 {
struct __block_impl impl;// block 結構體
struct __main_block_desc_0* Desc;// block 的描述物件
/*
block 的建構式
** 回傳值:__main_block_impl_0 結構體
** 引數一:__main_block_func_0 結構體
** 引數二:__main_block_desc_0 結構體的地址
** 引數三:flags 標識位
*/
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;// &_NSConcreteStackBlock 表示存盤在堆疊上
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block 結構體
struct __block_impl {
void *isa;//block 的型別
int Flags;
int Reserved;
void *FuncPtr;// block的執行函式指標,指向__main_block_func_0
};
//封裝了 block 中的代碼
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_03dcda_mi_0);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;// block 所占的記憶體空間
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
/*
** void(^block)(void) = ^{
NSLog(@"呼叫了block");
};
** 定義block的本質:
** 呼叫__main_block_impl_0()建構式
** 并且給它傳了兩個引數 __main_block_func_0 和 &__main_block_desc_0_DATA
** __main_block_func_0 封裝了block里的代碼
** 拿到函式的回傳值,再取回傳值的地址 &__main_block_impl_0,
** 把這個地址賦值給 block
*/
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/*
** block();
** 呼叫block的本質:
** 通過 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函式地址,直接呼叫
*/
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
3. block 的變數捕獲機制
- 對于全域變數,不會捕獲到 block 內部,訪問方式為直接訪問;
- 對于 auto 型別的區域變數,會捕獲到 block 內部,block 內部會自動生成一個成員變數,訪問方式為值傳遞;
- 對于 static 修飾的區域變數,會捕獲到 block 內部,block 內部會自動生成一個成員變數,訪問方式為指標傳遞,
- 對于物件型別的區域變數,block 會連同其所有權修飾符一起捕獲,
3.1 auto 自動變數
將以下 OC 代碼轉換為 C++ 代碼,并貼出部分變動代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
block();
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
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_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_40c716_mi_0,age);
}
可以看出:
- 在 __main_block_impl_0 結構體中會自動生成一個相同的 age 變數
- 建構式 __main_block_impl_0( ) 中多出了一個 age 引數,用來捕獲外部的變數
由于是傳遞方式為值傳遞,所以我們在 block 外部修飾 age 變數時,不會影響到 block 中的 age 變數
3.2 static 型別的區域變數
將以下 OC 代碼轉換為 C++ 代碼,并貼出部分變動代碼
static int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
age = 20;
block();
// 20
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
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_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_cb7943_mi_0,(*age));
}
可以看出:
- __main_block_impl_0 結構體中生成了一個相同型別的 age 指標
- __main_block_impl_0 建構式多了個引數,用來捕獲外部的 age 變數的地址
由于傳遞方式是指標傳遞,所以修改 區域變數 age 時,age 的值會隨之變化
3.3 全域變數
將以下 OC 代碼轉換為 C++ 代碼,并貼出部分變動代碼
int height = 10;
static int age = 20;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"%d,%d",height,age);
};
block();
}
return 0;
}
int height = 10;
static int age = 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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_7a340f_mi_0,height,age);
}
可以看出:
- __main_block_impl_0 結構體中,并沒有自動生成 age 或 height 全域變數,也就是說沒有將變數捕獲到 block 內部
那么就有一個問題:
為什么區域變數需要捕獲,而全域變數不用呢?
- 作用域的原因,全域變數哪里都可以直接訪問,所以不用捕獲;
- 區域變數,外部不能直接訪問,所以需要捕獲;
- auto 型別的區域變數可能會銷毀,其記憶體會消失,block 將來執行代碼的時候不可能再去訪問那塊記憶體,所以捕獲其值;
- static 變數會一直保存在記憶體中, 所以捕獲其地址即可,
3.4 __block 修飾的變數
3.4.1 __block 的使用:
終于來到那個問題:
默認情況下 block 是不能修改外面的 auto 變數的,解決辦法?
- 變數用 static 修飾(原因:捕獲 static 型別的區域變數是指標傳遞,可以訪問到該變數的記憶體地址)
- 全域變數
- __block(我們只希望臨時用一下這個變數臨時改一下而已,而改為 static 變數和全域變數會一直在記憶體中)
3.4.2 __block 修飾符
- __block 可以用于解決 block 內部無法修改 auto 變數值的問題;
- __block 不能修飾全域變數、靜態變數;
- 編譯器會將 __block 變數包裝成一個物件(
struct __Block_byref_age_0(byref:按地址傳遞)); - 加 __block 修飾不會修改變數的性質,它還是 auto 變數;
- 一般情況下,對被捕獲變數進行賦值(賦值!=使用)操作需要添加 __block 修飾符,比如給陣列添加或者洗掉物件,就不用加 __block 修飾;
- 在 MRC 下使用 __block 修飾物件型別,在 block 內部不會對該物件進行 retain 操作,所以在 MRC 環境下可以通過 __block 解決回圈參考的問題,
將以下 OC 代碼轉換為 C++ 代碼,并貼出部分變動代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
void(^block)(void) = ^{
age = 20;
NSLog(@"block-%d",age);
};
block();
NSLog(@"%d",age);
}
return 0;
}
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_75529b_mi_0,(age->__forwarding->age));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
可以看出:
- 編譯器會將 __block 修飾的變數包裝成一個
__Block_byref_age_0物件; - 以上
age = 20;的賦值程序為:通過 block 結構體里的(__Block_byref_age_0)型別的 age 指標,找到__Block_byref_age_0結構體的記憶體(即被 __block 包裝成物件的記憶體),把__Block_byref_age_0結構體里的 age 變數的值改為20,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/300277.html
標籤:其他
上一篇:【Android 教程系列第 9 篇】升級 Android Studio 到 Arctic Fox 2020.3.1 后,使用搜索功能時出現中文亂碼的問題
