1. 程式人生 > 其它 >OC筆記 - block實現原理 01:捕獲外部變數(基本型別)

OC筆記 - block實現原理 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變數捕獲機制