1. 程式人生 > >Objective-C 基礎之— Block本質+源碼剖析

Objective-C 基礎之— Block本質+源碼剖析

www IT -s 猜想 field char 源碼 等等 eof

block 又稱之為“自帶變量的匿名函數”,拋開OC語法定義block的形式不談,其實好多語言都有類似的函數,比如JS的回調函數(其實就是將一個匿名還是作為函數的實參)、swift的閉包等等。。
首先講一下oc block的實質,通過自身的理解,加以各位大神的剖析文章。block 在編譯時期會被編譯成結構體,也就是說OC的block底層是使用C語言結構體實現的, 和對象、類的實現是一樣的(所以其實block就是OC中的一個對象),這個結構體包括兩個結構體成員變量和一個構造函數,我們都知道Objective-C是用OC實現底層的,所以我們需要將代碼轉化為底層的實現(使用Clang -rewrite-objc [文件名], 這個命令是將OC語言轉化成C或者C++的代碼,經常使用這個命令我們窺探OC的底層奧秘)

.m 源碼 #import <Foundation/Foundation.h> int main(int argc, char * argv[]) { void (^myBlock)(void) = ^{ NSLog(@"hello world"); }; myBlock(); return 0; } clang 轉化成的過程源碼 // 此結構體 存儲了block的isa指針,還有函數指針(調用的時候正是找到此函數指針進行調用) struct __block_impl { void *isa; int Flags;
int Reserved; void *FuncPtr; }; // block函數執行體轉化 static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_dr_jkts5c395zs9xcx0bt_r7fx40000gn_T_main_7d68a8_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)}; // 轉化之後的main函數,我們可以看到block被轉化成了一個函數調用,這個函數正是 __main_block_impl_0 機構體的一個構造函數,所以block被轉化成了一個 __main_block_impl_0 類型的結構體; 我們看到構造函數傳入了兩個參數,一個函數指針,一個是結構體__main_block_desc_0實例 int main(int argc, char * argv[]) { // 其實這句話就是一個指針的賦值,將一個__main_block_impl_0結構體的指針賦值給我們定義的變量myBlock,從這裏可以看,OC的block會轉化成一個__main_block_impl_0類型結構體 void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); // block 的調用, 這句代碼的意思是找出__main_block_impl_0結構體的imp結構體(即是一個__block_impl的結構體)然後調用其函數指針FuncPtr,從而調用block內的函數執行體(當然這是過程代碼,並沒有直接標出myBlock.imp.FuncPtr) ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); return 0; } // 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; } };

上面的源碼轉換成C代碼之後,我做了一次完全的剖析,我們可以得出的結論就是,block 的實現其實就是一個結構體,而其的調用則是調用結構體內的函數指針調用函數;下面我將稍微改動一下代碼,看一下生成的底層代碼,研究一下,截獲的外部變量的情況:

我只將不同的代碼提出來 .m源代碼 int main(int argc, char * argv[]) { int a = 10; void (^myBlock)(void) = ^{ NSLog(@"hello world %d", a); }; myBlock(); return 0; } clang 轉換C代碼 // 我們看出來了這個時候__main_block_func_0的結構體的構造函數,多傳了一個變量a的參數,我們看到,並沒有把a的變量指針傳遞,而是將變量的值傳遞 int main(int argc, char * argv[]) { int a = 10; void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); return 0; } // block 函數執行體當然也會變了,生成了一個變量,這個變量是__main_block_impl_0結構體裏的變量a static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_dr_jkts5c395zs9xcx0bt_r7fx40000gn_T_main_3e8c5a_mi_0, a); } // 重要的結構體來了,這個結構體內生成了一個成員變量a struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };

那麽我們可以看出,再沒有任何修飾符的情況下,block截獲的變量只是一個值,而並不是指針,所以,內部執行函數無法改變block外部的變量
通過上邊的例子,我們可以猜想,如果block的結構體如果可以擁有變量的指針那麽,就可以修改外部變量的值了 ,所以 _ block就是做這件事情的,其實_ block的實現遠比我們想象的復雜,我們看一下__block 修飾之後轉換的代碼:

// 我們可以看到加了__block 的變量和不加的變量發生明顯的變化,加了__block的變量明顯被轉化成了一個結構體__Block_byref_c_0, 而且__main_block_impl_0block的結構體正是吧__Block_byref_c_0結構體類型的指針傳入了 int main(int argc, char * argv[]) { int a = 10; int b = 20; __attribute__((__blocks__(byref))) __Block_byref_c_0 c = {(void*)0,(__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 30}; void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b, (__Block_byref_c_0 *)&c, 570425344)); ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); return 0; } // 加了__block 的變量被轉化的結構提類型 struct __Block_byref_c_0 { void *__isa; __Block_byref_c_0 *__forwarding; int __flags; int __size; int c; }; // block的結構體也形成了一個__Block_byref_c_0結構體指針 struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; int b; __Block_byref_c_0 *c; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, __Block_byref_c_0 *_c, int flags=0) : a(_a), b(_b), c(_c->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; // block的執行函數 也相應的生成了一個C結構體的指針指向block結構的C static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_c_0 *c = __cself->c; // bound by ref int a = __cself->a; // bound by copy int b = __cself->b; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_dr_jkts5c395zs9xcx0bt_r7fx40000gn_T_main_55b164_mi_0, a, b, (c->__forwarding->c)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->c, (void*)src->c, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->c, 8/*BLOCK_FIELD_IS_BYREF*/);} static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0),

所以我們可以看得出來_ block 的作用是生成外部變量的指針,從而達到在block內部可以修改的目的;下一篇文章將會結合這篇文章Block的本質,總結_ weak _ block _ strong的使用。
致謝:https://blog.csdn.net/abc649395594/article/details/47086751
https://www.jianshu.com/p/fdd7fa9a9e7e
兩篇文章的對我的幫助!

Objective-C 基礎之— Block本質+源碼剖析