一、代碼 說明:本文章須結合文章《block本質探尋一之記憶體結構》和《class和object_getClass方法區別》加以理解; //main.m //列印 分析:很顯然,只有c的值沒有改變,其它變數的值都改變了——為什麼,看下底層代碼實現; 二、main.cpp 分析: 1)C語言語法 <1> ...
一、代碼
說明:本文章須結合文章《block本質探尋一之記憶體結構》和《class和object_getClass方法區別》加以理解;
//main.m
#import <Foundation/Foundation.h> int a = 10; static int b = 10; int main(int argc, const char * argv[]) { @autoreleasepool { auto int c = 20; static int d = 20; void (^block)(void) = ^{ NSLog(@"a=%d, b=%d, c=%d, d=%d", a, b, c, d); }; a = 30; b = 35; c = 40; d = 45; block(); } return 0; }
//列印
2019-01-09 15:42:16.246684+0800 MJ_TEST[4180:224738] a=30, b=35, c=20, d=45 Program ended with exit code: 0
分析:很顯然,只有c的值沒有改變,其它變數的值都改變了——為什麼,看下底層代碼實現;
二、main.cpp
int a = 10; static int b = 10; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int c; int *d; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _c, int *_d, int flags=0) : c(_c), d(_d) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int c = __cself->c; // bound by copy int *d = __cself->d; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_1f1f41_mi_0, a, b, c, (*d)); } 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 c = 20; static int d = 20; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, c, &d)); a = 30; b = 35; c = 40; d = 45; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
分析:
1)C語言語法
<1>int c被轉換成auto int c:我們知道c、d為局部變數,而a、b為全局變數,C語言中,所有沒有修飾符的局部變數預設的修飾符為auto,static修飾的變數為靜態變數,還有一個register註冊類的在此不再贅述(自己有興趣上網查下);
<2>auto類型的局部變數的生命周期為離其最近的大括弧內,超出該大括弧,該變數被自動銷毀;
<3>static類型的變數(不論是全局還是局部),其值一直保留在記憶體中,不受大括弧的限制,程式結束時才被銷毀;
2)變數捕獲概念
我們發現在block結構體中,存在c、d而不存在a、b變數,在此,我們把存在於block結構體中的外部變數稱為變數捕獲,不存在的則沒有被捕獲,所以a、b變數沒有被block捕獲;
3)變數調用流程
<1>在block結構體中,c保持不變依然為int型變數,而d被轉換成int型指針變數,因此在main函數中通過__main_block_impl_0方法傳遞實參c本身的值和d指向的記憶體的值&d;
而在block的構造函數中,c(_c), d(_d)為C++語法<=>c = _c,d = _d,那麼main函數中的實參c、&d最終傳遞給了block結構體中的變數c和指針變數d;
<2>最後在__main_block_func_0方法中,對c、d而言,須先獲取block內部的成員變數再輸出;而對於a、b,因為是全局變數,所以可以直接引用;
綜上所述:
auto局部變數因為作用域(或生命周期)有限,隨時會銷毀,故block在引用時系統會自動將其值保存在block結構體中(即捕獲);而全局變數和static修飾的變數(局部或全局),並不會隨時被銷毀,其值一直會在記憶體中保持不變,知道整個程式結束時才銷毀
1)另外從另一個角度理解,全局變數其作用域為從其定義的地方開始到該文件結束止都是有效的,所以main函數中可以用,__main_block_func_0函數中也可以用,不需要再將其保存到block自身的結構體中;
2)static修飾的局部變數會被轉化成指針變數,而保存到block結構體中也是指針,因為指針本身的值為另一個變數的地址,所以block對該指針的操作始終是對另一個變數的地址的操作,而非另一個變數值的本身,當對d重新賦值時,block中的指針變數指向的變數的值也就隨之改變,對*d輸出當然被改變(*d即取出指向的記憶體地址存放的值);
3)auto局部變數被捕獲,即是在記憶體中重新開闢了記憶體來存放該變數的值(即copy),只不過是在block結構體對應的記憶體中;
三、結論
1.
2.auto修飾的局部變數在block定義後的修改,不影響block內部對該變數的使用;後兩者,有影響;
四、擴展——OC對象捕獲問題
1)Person.m——註:此處我將.h文件也貼過來了,為了很好的閱讀
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface Person : NSObject @property (nonatomic, copy) NSString *name; - (instancetype)initWithName:(NSString *)name; @end NS_ASSUME_NONNULL_END #import "Person.h" int weight_ = 10; @implementation Person - (void)test { void(^block)(void) = ^{ NSLog(@"-----%p", self); NSLog(@"-----%@", _name); NSLog(@"-----%@", self.name); NSLog(@"-----%d", weight_); }; } - (instancetype)initWithName:(NSString *)name { self = [super init]; if (self) { } return self; } @end
2)Person.cpp——註:此處只對.m文件進行轉化
問題一:參數
static void _I_Person_test(Person * self, SEL _cmd) { void(*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344)); } static instancetype _Nonnull _I_Person_initWithName_(Person * self, SEL _cmd, NSString * _Nonnull name) { self = ((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("init")); if (self) { } return self; }
分析:
<1>在.m文件內部有兩個方法test和initWithName,前者不帶參數,後者帶一個參數name,但是轉成C++後,發現,兩個方法前面均自動加上了兩個參數:Person * self, SEL _cmd;這是每個方法必備的兩個參數,前者是調用對象本身self,後者是方法名;
<2>此處的self為實例對象而非類對象(驗證方法:在test方法中列印self的地址%p,會發現每次調用的值都不一樣,而類對象在記憶體中只有一份;
問題二:self捕獲
struct __Person__test_block_impl_0 { struct __block_impl impl; struct __Person__test_block_desc_0* Desc; Person *self; __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
分析:
<1>self被捕獲到block結構體體中,那麼可以肯定self是auto類型的局部變數;
<2>從另一個角度理解:self作為實參從main函數中傳遞到block結構體的構造函數__Person__test_block_impl_0的形參_self,再將_slef賦值於self;而參數本身就是一個auto類型的局部變數,函數結束後就自動被銷毀;
問題三:block代碼塊執行
int weight_ = 10; static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) { Person *self = __cself->self; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_0, self); NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_1, (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_name))); NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_2, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name"))); NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_Person_952ecd_mi_3, weight_); }
分析:
<1>self作為auto類型的局部變數,輸出前需先從block結構體中取出該成員變數;
<2>weight作為全局變數,直接引用,無須捕獲;
<3>以_name來引用對象屬性,其本質是block的成員變數,存放在類對象的結構體記憶體中,而結構體指針變數須通過"->"來引用該結構體成員變數,但是self作為實例對象與Person類對象不是同一個,問什麼能通過"->"來引用?
————原因:self的第一個成員變數為isa,而isa是指向類對象的指針,即類對象的首地址跟實例對象的首地址是同一個地址,而結構體成員變數在記憶體中的地址是連續的,因此self可以通過"->"形式來找到_name成員變數;
<4>self.name即通過getter方法來訪問name值,轉換成C++為objc_msgSend方法,即通過消息轉發機制來訪問name值,而消息轉發機制的本質是通過isa來找到類對象,進而訪問該類對象中的name成員變數;
3)結論
【1】oc實例對象self會被捕獲到block結構體中;
【2】@property聲明的屬性的引用,須先執行【1】步驟,因此嚴格上講也是被捕獲到block中;
【3】.m文件中聲明的全局變數,不受self影響,依然不會被捕獲到block中,;