詳解Objective C 中Block如何捕獲外部值
引言
Block
本質(zhì)上也是一個(gè) Objective-C
對(duì)象,它內(nèi)部也有個(gè) isa
指針。Block
是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的 Objective-C
對(duì)象。Block
的底層結(jié)構(gòu)如下圖所示:
Block
對(duì)于不同類型的值會(huì)有不同的捕獲方式,本文將通過(guò)代碼展示其對(duì)于各種場(chǎng)景下的外部值是如何進(jìn)行捕獲的。
自動(dòng)變量
首先展示源代碼:
int main(int argc, const char * argv[]) { @autoreleasepool { NSInteger value = 0; void(^block)(void) = ^{ NSLog(@"%zd", value); }; block(); } return 0; }
經(jīng)過(guò) clang -rewrite-objc
之后,得到的代碼如下,可以看到,對(duì)于自動(dòng)變量的捕獲,是會(huì)在 Block
結(jié)構(gòu)體中生成一個(gè)對(duì)應(yīng)類型的成員變量來(lái)實(shí)現(xiàn)捕獲的能力,這也解釋了為什么在 Block
中修改捕獲的值的內(nèi)容,無(wú)法對(duì) Block
外的值產(chǎn)生影響。
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSInteger value; // 捕獲的 NSInteger value __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _value, int flags=0) : value(_value) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSInteger value = __cself->value; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_e3ca95_mi_0, value); } 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; NSInteger value = 0; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, value)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
靜態(tài)變量、靜態(tài)全局變量與全局變量
對(duì)于靜態(tài)變量、靜態(tài)全局變量與全局變量的捕獲,會(huì)稍有不同,其中:
- 全局變量與靜態(tài)全局變量:直接使用,因?yàn)榈刂芬恢笔强梢灾苯荧@取的。
- 靜態(tài)變量:捕獲地址使用,因?yàn)?
block
有可能會(huì)傳遞出創(chuàng)建時(shí)的作用域。
NSInteger globalValue = 1; static NSInteger staticGlobalValue = 2; int main(int argc, const char * argv[]) { @autoreleasepool { static NSInteger staticValue = 3; void(^block)(void) = ^{ globalValue += 1; staticGlobalValue += 2; staticValue += 3; }; block(); } return 0; }
NSInteger globalValue = 1; static NSInteger staticGlobalValue = 2; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSInteger *staticValue; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger *_staticValue, int flags=0) : staticValue(_staticValue) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSInteger *staticValue = __cself->staticValue; // bound by copy globalValue += 1; staticGlobalValue += 2; (*staticValue) += 3; } 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; static NSInteger staticValue = 3; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticValue)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
帶 __block 的自動(dòng)變量
被 __block
修飾的自動(dòng)變量,可以在 Block
內(nèi)部對(duì)其外部的值進(jìn)行修改:
int main(int argc, const char * argv[]) { @autoreleasepool { __block NSInteger value = 0; void(^block)(void) = ^{ value = 10; }; block(); NSLog(@"%zd", value); } return 0; }
這次生成的代碼復(fù)雜了一些,不過(guò)只關(guān)注 value
部分的話可以發(fā)現(xiàn),Block
為了捕獲 __block
類型的自動(dòng)變量,會(huì)生成 __Block_byref_value_0
結(jié)構(gòu)體,并通過(guò)該結(jié)構(gòu)體來(lái)實(shí)現(xiàn)對(duì)外部 __block
自動(dòng)變量的捕獲。
struct __Block_byref_value_0 { // 為捕獲 __block 的自動(dòng)變量,生成的結(jié)構(gòu)體。為的是方便多個(gè) Block 同時(shí)捕獲一個(gè)自動(dòng)變量時(shí)使用。 void *__isa; // isa 指針 __Block_byref_value_0 *__forwarding; // 在 Block 單純?cè)跅I鲜牵赶虻氖亲约?,拷貝到堆上后,指向的是在堆上?Block。之所以需要這樣的指針是因?yàn)楫?dāng) Block 拷貝到堆上時(shí),調(diào)用方式是統(tǒng)一的。 int __flags; int __size; NSInteger value; // 具體的值 }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_value_0 *value; // 通過(guò)引用的方式捕獲 value,其中變量類型為 __Block_byref_value_0 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : value(_value->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_value_0 *value = __cself->value; // bound by ref (value->__forwarding->value) = 10; // 賦值代碼 } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->value, (void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->value, 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), __main_block_copy_0, __main_block_dispose_0}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_value_0 value = {(void*)0,(__Block_byref_value_0 *)&value, 0, sizeof(__Block_byref_value_0), 0}; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_value_0 *)&value, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_6bf1c6_mi_0, (value.__forwarding->value)); } return 0; }
__block
可以用于解決 block
內(nèi)部無(wú)法修改 auto
變量值的問(wèn)題,__block
不能修飾全局變量、靜態(tài)變量(static
),編譯器會(huì)將 __block
變量包裝成一個(gè)對(duì)象。
當(dāng) block
在棧上時(shí),并不會(huì)對(duì) __block
變量產(chǎn)生強(qiáng)引用。
當(dāng) block
被 copy
到堆時(shí),會(huì)調(diào)用 block
內(nèi)部的 copy
函數(shù),copy
函數(shù)內(nèi)部會(huì)調(diào)用 _Block_object_assign
函數(shù),_Block_object_assign
函數(shù)會(huì)對(duì) __block
變量形成強(qiáng)引用(retain
)。
當(dāng) block
從堆中移除時(shí),會(huì)調(diào)用 block
內(nèi)部的 dispose
函數(shù),dispose
函數(shù)內(nèi)部會(huì)調(diào)用 _Block_object_dispose
函數(shù),_Block_object_dispose
函數(shù)會(huì)自動(dòng)釋放引用的 __block
變量(release
)。
捕獲對(duì)象
在探究完對(duì)標(biāo)量類型的捕獲之后,讓我們看一下對(duì)對(duì)象類型的捕獲:
int main(int argc, const char * argv[]) { @autoreleasepool { NSArray *array = [NSArray array]; void(^block)(void) = ^{ NSLog(@"%@", array); }; block(); } return 0; }
通過(guò)轉(zhuǎn)譯的代碼可以看出,因?yàn)閷?duì)象類型本身已經(jīng)是存儲(chǔ)在堆上的值了,所以直接獲取其地址即可,同時(shí)其新增了兩個(gè)函數(shù) __main_block_copy_0
和 __main_block_dispose_0
,這兩個(gè)函數(shù)是用來(lái)將對(duì)象拷貝到堆上和被從堆上移除時(shí)調(diào)用的,其內(nèi)部又分別調(diào)用了 _Block_object_assign
和 _Block_object_dispose
用來(lái)對(duì)捕獲的對(duì)象進(jìn)行引用計(jì)數(shù)的增加和減少。
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSArray *array; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSArray *_array, int flags=0) : array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSArray *array = __cself->array; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_8ba4f7_mi_0, array); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);} 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), __main_block_copy_0, __main_block_dispose_0}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSArray *array = ((NSArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array")); void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
Block
對(duì)象本身分為三種類型:
- NSGlobalBlock:沒(méi)有訪問(wèn)
auto
變量,調(diào)用copy
方法之后不會(huì)發(fā)生變化。 - NSStackBlock:訪問(wèn)了
auto
變量,調(diào)用copy
方法之后存儲(chǔ)位置從棧變?yōu)槎选?/li> - NSMallocBlock:
__NSStackBlock__
調(diào)用了copy
方法之后,引用計(jì)數(shù)增加。
在 ARC
環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的 block
復(fù)制到堆上,比如以下情況:
Block
作為函數(shù)返回值時(shí)- 將
Block
賦值給__strong
指針時(shí) Block
作為Cocoa API
中方法名含有usingBlock
的方法參數(shù)時(shí)Block
作為GCD API
的方法參數(shù)時(shí)
所以,當(dāng) Block
內(nèi)部訪問(wèn)了對(duì)象類型的 auto
變量時(shí)。如果 Block
是在棧上,將不會(huì)對(duì) auto
變量產(chǎn)生強(qiáng)引用。
如果 Block
被拷貝到堆上,會(huì)調(diào)用 Block
內(nèi)部的 copy
函數(shù),copy
函數(shù)內(nèi)部會(huì)調(diào)用 _Block_object_assign
函數(shù),_Block_object_assign
函數(shù)會(huì)根據(jù) auto
變量的修飾符(__strong
、__weak
、__unsafe_unretained
)做出相應(yīng)的操作,形成強(qiáng)引用或者弱引用。
如果 Block
從堆上移除,會(huì)調(diào)用 Block
內(nèi)部的 dispose
函數(shù),dispose
函數(shù)內(nèi)部會(huì)調(diào)用 _Block_object_dispose
函數(shù)。_Block_object_dispose
函數(shù)會(huì)自動(dòng)釋放引用的 auto
變量(release
)。
__block 對(duì)象類型的捕獲
如果想在 Block
中,對(duì)捕獲的對(duì)象的指針指向進(jìn)行修改,則需要添加 __block
關(guān)鍵字:
int main(int argc, const char * argv[]) { @autoreleasepool { __block NSArray *array = [NSArray array]; void(^block)(void) = ^{ array = [NSArray array]; NSLog(@"%@", array); }; block(); } return 0; }
通過(guò)轉(zhuǎn)譯我們可以看出,跟 __block
修飾的標(biāo)量類型相似,同樣會(huì)生成 __Block_byref_array_0
結(jié)構(gòu)體來(lái)捕獲對(duì)象類型。同時(shí)其內(nèi)部生成了 __Block_byref_id_object_copy
和 __Block_byref_id_object_dispose
兩個(gè)函數(shù)指針,用于對(duì)被結(jié)構(gòu)體包裝的對(duì)象進(jìn)行內(nèi)存管理。
static void __Block_byref_id_object_copy_131(void *dst, void *src) { _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131); } static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void * *) ((char*)src + 40), 131); } struct __Block_byref_array_0 { void *__isa; __Block_byref_array_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSArray *array; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_array_0 *array; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_array_0 *array = __cself->array; // bound by ref (array->__forwarding->array) = ((NSArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array")); NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_3593f0_mi_0, (array->__forwarding->array)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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), __main_block_copy_0, __main_block_dispose_0}; int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"))}; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array_0 *)&array, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
當(dāng) block
在棧上時(shí),對(duì)它們都不會(huì)產(chǎn)生強(qiáng)引用。
當(dāng) block
拷貝到堆上時(shí),都會(huì)通過(guò) copy
函數(shù)來(lái)處理它們,__block
變量(假設(shè)變量名叫做 a
):
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
對(duì)象類型的 auto
變量(假設(shè)變量名叫做 p
):
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
當(dāng) block
從堆上移除時(shí),都會(huì)通過(guò) dispose
函數(shù)來(lái)釋放它們,__block
變量(假設(shè)變量名叫做 a
):
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
對(duì)象類型的 auto
變量(假設(shè)變量名叫做 p
):
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
以上就是詳解Objective C 中Block如何捕獲外部值的詳細(xì)內(nèi)容,更多關(guān)于Objective C Block捕獲外部值的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
iOS使用UIBezierPath實(shí)現(xiàn)ProgressView
這篇文章主要為大家詳細(xì)介紹了iOS使用UIBezierPath實(shí)現(xiàn)ProgressView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04iOS使用GCDSocketManager實(shí)現(xiàn)長(zhǎng)連接的方法
下面想就為大家分享一篇iOS使用GCDSocketManager實(shí)現(xiàn)長(zhǎng)連接的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12詳解iOS中Button按鈕的狀態(tài)和點(diǎn)擊事件
這篇文章先是給大家介紹iOS中Button按鈕的狀態(tài),而后又詳細(xì)介紹了iOS中按鈕點(diǎn)擊事件處理方式,本文介紹的很詳細(xì),有需要的朋友們可以參考借鑒,下面來(lái)一起看看吧。2016-09-09ios實(shí)現(xiàn)UITableView之間圓角和間隙
這篇文章主要為大家詳細(xì)介紹了ios實(shí)現(xiàn)UITableView之間圓角和間隙,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08iOS實(shí)現(xiàn)一個(gè)可以在屏幕中自由移動(dòng)的按鈕
經(jīng)常在手機(jī)上看到可以隨意移動(dòng)的按鈕,正巧最近工作遇到了這個(gè)需求,索性就寫(xiě)一個(gè),下面這篇文章主要給大家介紹了利用iOS實(shí)現(xiàn)一個(gè)可以在屏幕中自由移動(dòng)的按鈕的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-07-07