OC筆記 - block實現原理 01:捕獲外部變數(基本型別)
阿新 • • 發佈:2022-06-01
block底層實現
1 - 我們先看最簡單的 block
① 定義一個無返回值無參型 block
1 #import <Foundation/Foundation.h> 2 int main(int argc, const char * argv[]) { 3 4 @autoreleasepool { 5 6 // 定義 block01 7 void(^block01)(void) = ^{ 8 9 NSLog(@"hello block"); 10 };11 12 // 回撥 13 block01(); 14 } 15 16 return 0; 17 18 }
② 編譯成 C++程式碼
1 struct __main_block_impl_0 { 2 struct __block_impl impl; 3 struct __main_block_desc_0* Desc; 4 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { 5 impl.isa = &_NSConcreteStackBlock;6 impl.Flags = flags; 7 impl.FuncPtr = fp; 8 Desc = desc; 9 } 10 }; 11 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 12 13 14 NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_bacace_mi_0); 15 }16 17 static struct __main_block_desc_0 { 18 size_t reserved; 19 size_t Block_size; 20 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; 21 int main(int argc, const char * argv[]) { 22 23 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 24 25 void(*block01)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 26 27 ((void (*)(__block_impl *))((__block_impl *)block01)->FuncPtr)((__block_impl *)block01); 28 } 29 30 return 0; 31 32 }
③ 程式碼分析
首先我們把 C++程式碼變成 OC格式
1 int main(int argc, const char * argv[]) { 2 3 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 4 5 // 定義 block01變數 6 void(*block01)(void) = &__main_block_impl_0(__main_block_func_0, 7 &__main_block_desc_0_DATA 8 ); 9 10 // 執行 block01內部程式碼 11 block01->FuncPtr(block01);// 呼叫 block01函式指標變數指向的函式 __main_block_impl_0 12 } 13 14 return 0; 15 16 }
引數 __main_block_func_0
1 // 封裝了 block01內部的執行函式 2 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 3 4 // 這就是 blocl01內部的程式碼塊 NSLog 5 NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_bacace_mi_0); 6 }
引數 __main_block_desc_0
1 static struct __main_block_desc_0 { 2 3 size_t reserved; // 0 4 size_t Block_size; // 就是 struct __main_block_impl_0的大小 5 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; // 結構體成員變數賦值
那麼這時候回過頭來看 __main_block_impl_0函式的功能就一目瞭然了
1 struct __main_block_impl_0 { 2 struct __block_impl impl; // 封裝了 block資訊:大小、型別、內部執行函式等等 3 struct __main_block_desc_0* Desc; // block01大小 4 5 // C++的建構函式(類似 OC中的構造方法),它返回自身結構體__main_block_impl_0 6 // flags=0 預設值是 0,就是說這個引數可以不傳 7 __main_block_impl_0(void *fp, 8 struct __main_block_desc_0 *desc, 9 int flags=0) { 10 impl.isa = &_NSConcreteStackBlock;// block是什麼型別 11 impl.Flags = flags; 12 impl.FuncPtr = fp; // __main_block_func_0 13 Desc = desc; // __main_block_impl_0 就是我們的 block01的大小 14 } 15 };
最終關係圖示如下
2 - 捕獲外部變數(基本型別)
① block02內部使用全域性變數、全域性靜態變數、區域性變數、區域性靜態變數
1 #import <Foundation/Foundation.h> 2 // 全域性變數 3 int pets = 1000; 4 // 全域性靜態變數 5 static int army = 2000; 6 7 int main(int argc, const char * argv[]) { 8 9 @autoreleasepool { 10 11 // 修飾變數的有三種 auto(自動變數:值傳遞)、static(靜態變數:指標傳遞)、register(暫存器) 12 // 自動變數:離開作用域就銷燬 13 int age = 10;// 預設是 auto,它只存在區域性變數中 14 15 // 區域性靜態變數 16 static int height = 30; 17 18 void(^block02)(void) = ^{ 19 20 NSLog(@"自動變數 is :%d",age); 21 NSLog(@"區域性靜態變數 is :%d",height); 22 NSLog(@"全域性變數 is :%d",pets); 23 NSLog(@"全域性靜態變數 is :%d",army); 24 }; 25 26 age = 300; 27 height = 500; 28 pets = 20000; 29 army = 30000; 30 block02(); 31 // 自動變數 is :10 32 // 區域性靜態變數 is :500 33 // 全域性變數 is :20000 34 // 全域性靜態變數 is :30000 35 } 36 37 return 0; 38 39 }
② 編譯成 C++(排版成 OC格式)
1 // 全域性變數不會 block被捕獲 2 int pets = 1000; 3 static int army = 2000; 4 5 struct __main_block_impl_0 { 6 7 struct __block_impl impl; 8 struct __main_block_desc_0* Desc; 9 // 區域性(靜態)變數會被捕獲到 block內部 10 int age; // 區域性變數:值訪問 11 int *height; // 區域性靜態變數:地址訪問 12 // age(_age), height(_height) C++語法就是賦值的意思 age = _age;height = _height 13 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) { 14 impl.isa = &_NSConcreteStackBlock; 15 impl.Flags = flags; 16 impl.FuncPtr = fp; 17 Desc = desc; 18 } 19 }; 20 21 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 22 23 int age = __cself->age; 24 int *height = __cself->height; 25 26 // 區域性變數:捕獲值型別 27 NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_f264bf_mi_0,age); 28 // 區域性靜態變數:捕獲的是地址 29 NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_f264bf_mi_1,(*height)); 30 // 全域性(靜態)變數:並會不捕獲到 block內部,使用時直接取出使用 31 NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_f264bf_mi_2,pets); 32 NSLog((NSString *)&__NSConstantStringImpl__var_folders_t5_d7bd_spn3hsfdqj994fdm9bh0000gn_T_main_f264bf_mi_3,army); 33 } 34 35 static struct __main_block_desc_0 { 36 size_t reserved; 37 size_t Block_size; 38 } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; 39 40 41 // main函式 42 int main(int argc, const char * argv[]) { 43 44 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 45 46 int age = 10; 47 static int height = 30; 48 // 注意傳參的不同 49 // 區域性變數 age 區域性靜態變數 &height 50 void(*block02)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height)); 51 52 age = 300; 53 height = 500; 54 pets = 20000; 55 army = 30000; 56 block02->FuncPtr(block02); 57 } 58 59 return 0; 60 61 }
小問題
1 - 分析以下程式碼
// - Person.h
1 #import <Foundation/Foundation.h> 2 @interface Person : NSObject 3 4 @property(nonatomic,copy)NSString *name; 5 -(void)test; 6 @end
// - Person.m
1 #import "Person.h" 2 @implementation Person 3 4 -(void)test{ 5 6 void(^blockA)(void) = ^{ 7 8 // 問題一:blockA內部會不會捕獲 self 9 NSLog(@"%@",self); 10 11 // 問題二:blockA內部捕獲的又是什麼 12 NSLog(@"%@",_name); 13 14 // 問題三:blockA內部捕獲的東東又是什麼 15 NSLog(@"%@",[self name]); 16 }; 17 18 blockA(); 19 } 20 21 22 @end
結論:不管是問題一還是問題二、問題三,其 block內部捕獲的都是區域性變數 Person *self
問題一:這個不難理解,block 內部捕獲區域性變數 Person *self
問題二:訪問例項物件的成員變數 _name,其實質是 block內部捕獲的變數是 Person *self,通過 self取出成員變數
問題三:訪問例項物件的 name方法,同樣地 block內部捕獲的依舊是 Person *self,通過 self找到它的方法
注:OC中的方法都預設有兩個引數 self和 _cmd,既然是引數,那麼 self可以理解成一個區域性變數,當然可以被 block捕獲
結語
1 - block變數捕獲機制