古巷悠悠歲月深,青石老街印舊痕
今夜小樓聽風雨,不見當年傘下人面試
Block做爲iOS中老生常談的問題,也是面試中面試官比較喜歡問的 一個問題 ,下面咱們經過源碼查看block的底層實現原理函數
Block:將函數
及其上下文
組裝起來的對象
spa
建立一個PHJBlock類指針
@implementation PHJBlock - (void)test { int a = 10; void (^ block)(void) = ^{ NSLog(@"%d", a); }; block(); } @end
查看編譯後的C++源碼code
編譯: Clang -rewrite-objc PHJBlock.m對象
static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) { int a = 10; void (* block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, a)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }
__PHJBlock__test_block_impl_0的內部實現圖片
struct __PHJBlock__test_block_impl_0 { struct __block_impl impl; struct __PHJBlock__test_block_desc_0* Desc; int a; __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
__block_impl的內部實現ip
看到isa,證實block的本質就是一個對象
內存
struct __block_impl { void *isa; // 看到isa,證實block的本質就是一個對象 int Flags; int Reserved; void *FuncPtr; };
棧Block
堆Block
全局Blockcmd
int a = 10; // 堆block void(^block)(void) = ^{ NSLog(@"%d", a); }; block(); NSLog(@"%@", block); // 全局block void(^block1)(void) = ^{ }; block1(); NSLog(@"%@", block1); // 棧block NSLog(@"%@", ^{ NSLog(@"%d", a); });
打印查看block內存地址
局部變量
基本數據類型:截獲其值 對象類型:對於對象類型的局部變量連同全部權修飾符一塊兒截獲
靜態局部變量
以指針形式
全局變量
不截獲
靜態全局變量
不截獲
不加修飾詞修飾的變量
- (void)test { int a = 100; void(^block)(void) = ^{ printf("%d", a); }; block(); }
編譯後的C++代碼:捕獲外部變量的值
static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) { // 捕獲 a 的值 int a = __cself->a; // bound by copy printf("%d", a); }
加修飾詞修飾的變量
- (void)test { __block int a = 100; void(^block)(void) = ^{ a ++; printf("%d", a); }; block(); }
編譯後的C++代碼:捕獲外部變量的的地址
總結:這也就解釋了爲何咱們在外部變量前加上__block就能在block內部能夠修改變量的值
static void __PHJBlock__test_block_func_0(struct __PHJBlock__test_block_impl_0 *__cself) { // 捕獲 a 的地址 __Block_byref_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) ++; printf("%d", (a->__forwarding->a)); }
發現變量a加上__block後變成了一個對象__forwarding指針
struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; };
- (void)test { __unsafe_unretained id obj = nil; __strong NSObject *obj1 = nil; void(^block)(void) = ^{ NSLog(@"__unsafe_unretained類型變量:%@", obj); NSLog(@"__strong類型變量:%@", obj1); }; block(); }
編譯後的C++代碼:連同外部變量的修飾詞一塊兒捕獲
static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) { // 修飾詞一塊兒捕獲 __attribute__((objc_ownership(none))) id obj = __null; __attribute__((objc_ownership(strong))) NSObject *obj1 = __null; void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, obj, obj1, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }
總結:這也就解釋了爲何咱們在外部對象前加上__weak就能在block內部使用的時候能夠避免循環引用問題
- (void)test { static int a = 100; void(^block)(void) = ^{ NSLog(@"static類型變量a :%d", a); }; block(); }
編譯後的C++代碼:捕獲變量的地址
&a
表明傳入的是a變量的地址
static void _I_PHJBlock_test(PHJBlock * self, SEL _cmd) { static int a = 100; // 下面的&a表明傳入的是a變量的地址 void(*block)(void) = ((void (*)())&__PHJBlock__test_block_impl_0((void *)__PHJBlock__test_block_func_0, &__PHJBlock__test_block_desc_0_DATA, &a)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }
int *a
接收傳入的a變量的地址
struct __PHJBlock__test_block_impl_0 { struct __block_impl impl; struct __PHJBlock__test_block_desc_0* Desc; // 捕獲a變量的地址 int *a; __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
int a = 100; @implementation PHJBlock - (void)test { void(^block)(void) = ^{ NSLog(@"全局變量a :%d", a); }; block(); } @end
編譯後的C++代碼:不會捕獲全局變量
struct __PHJBlock__test_block_impl_0 { struct __block_impl impl; struct __PHJBlock__test_block_desc_0* Desc; __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
static int a = 100; @implementation PHJBlock - (void)test { void(^block)(void) = ^{ NSLog(@"全局變量a :%d", a); }; block(); } @end
編譯後的C++代碼:不會捕獲靜態全局變量
struct __PHJBlock__test_block_impl_0 { struct __block_impl impl; struct __PHJBlock__test_block_desc_0* Desc; __PHJBlock__test_block_impl_0(void *fp, struct __PHJBlock__test_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
須要加__block
__block NSMutableDictionary *dicM = nil; void(^block)(void) = ^{ dicM = [NSMutableDictionary dictionary]; }; block();
不須要加__block
NSMutableDictionary *dicM = nil; void(^block)(void) = ^{ [dicM setObject:@(1) forKey:@"1"]; }; block();
總結:賦值的時候須要加__block,操做使用的時候不用加
棧Block沒有進行copy操做
棧__forwarding指針都指向棧中本身的變量
棧Block若是進行了copy操做
棧和堆上的__forwarding指針都指向堆的變量
總結:不論在任何內存位置,均可以順利訪問同一個__block變量
棧block進行copy,獲得堆block 堆block進行copy,增長引用計數 全局block進行copy,block不會產生影響
MRC下,不會產生循環引用 在ARC下,會產生循環引用,引發內存泄漏,解決:在block內部對__block修飾的對象變量進行置nil操做