您好,登錄后才能下訂單哦!
本篇內容介紹了“Objective C中Block怎么捕獲外部值”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Block
本質上也是一個 Objective-C
對象,它內部也有個 isa
指針。Block
是封裝了函數調用以及函數調用環境的 Objective-C
對象。Block
的底層結構如下圖所示:
Block
對于不同類型的值會有不同的捕獲方式,本文將通過代碼展示其對于各種場景下的外部值是如何進行捕獲的。
首先展示源代碼:
int main(int argc, const char * argv[]) { @autoreleasepool { NSInteger value = 0; void(^block)(void) = ^{ NSLog(@"%zd", value); }; block(); } return 0; }
經過 clang -rewrite-objc
之后,得到的代碼如下,可以看到,對于自動變量的捕獲,是會在 Block
結構體中生成一個對應類型的成員變量來實現捕獲的能力,這也解釋了為什么在 Block
中修改捕獲的值的內容,無法對 Block
外的值產生影響。
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; }
對于靜態變量、靜態全局變量與全局變量的捕獲,會稍有不同,其中:
全局變量與靜態全局變量:直接使用,因為地址一直是可以直接獲取的。
靜態變量:捕獲地址使用,因為 block
有可能會傳遞出創建時的作用域。
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
修飾的自動變量,可以在 Block
內部對其外部的值進行修改:
int main(int argc, const char * argv[]) { @autoreleasepool { __block NSInteger value = 0; void(^block)(void) = ^{ value = 10; }; block(); NSLog(@"%zd", value); } return 0; }
這次生成的代碼復雜了一些,不過只關注 value
部分的話可以發現,Block
為了捕獲 __block
類型的自動變量,會生成 __Block_byref_value_0
結構體,并通過該結構體來實現對外部 __block
自動變量的捕獲。
struct __Block_byref_value_0 { // 為捕獲 __block 的自動變量,生成的結構體。為的是方便多個 Block 同時捕獲一個自動變量時使用。 void *__isa; // isa 指針 __Block_byref_value_0 *__forwarding; // 在 Block 單純在棧上是,指向的是自己,拷貝到堆上后,指向的是在堆上的 Block。之所以需要這樣的指針是因為當 Block 拷貝到堆上時,調用方式是統一的。 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; // 通過引用的方式捕獲 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
內部無法修改 auto
變量值的問題,__block
不能修飾全局變量、靜態變量(static
),編譯器會將 __block
變量包裝成一個對象。
當 block
在棧上時,并不會對 __block
變量產生強引用。
當 block
被 copy
到堆時,會調用 block
內部的 copy
函數,copy
函數內部會調用 _Block_object_assign
函數,_Block_object_assign
函數會對 __block
變量形成強引用(retain
)。
當 block
從堆中移除時,會調用 block
內部的 dispose
函數,dispose
函數內部會調用 _Block_object_dispose
函數,_Block_object_dispose
函數會自動釋放引用的 __block
變量(release
)。
在探究完對標量類型的捕獲之后,讓我們看一下對對象類型的捕獲:
int main(int argc, const char * argv[]) { @autoreleasepool { NSArray *array = [NSArray array]; void(^block)(void) = ^{ NSLog(@"%@", array); }; block(); } return 0; }
通過轉譯的代碼可以看出,因為對象類型本身已經是存儲在堆上的值了,所以直接獲取其地址即可,同時其新增了兩個函數 __main_block_copy_0
和 __main_block_dispose_0
,這兩個函數是用來將對象拷貝到堆上和被從堆上移除時調用的,其內部又分別調用了 _Block_object_assign
和 _Block_object_dispose
用來對捕獲的對象進行引用計數的增加和減少。
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
對象本身分為三種類型:
NSGlobalBlock:沒有訪問 auto
變量,調用 copy
方法之后不會發生變化。
NSStackBlock:訪問了 auto
變量,調用 copy
方法之后存儲位置從棧變為堆。
NSMallocBlock:__NSStackBlock__
調用了 copy
方法之后,引用計數增加。
在 ARC
環境下,編譯器會根據情況自動將棧上的 block
復制到堆上,比如以下情況:
Block
作為函數返回值時
將 Block
賦值給 __strong
指針時
Block
作為 Cocoa API
中方法名含有 usingBlock
的方法參數時
Block
作為 GCD API
的方法參數時
所以,當 Block
內部訪問了對象類型的 auto
變量時。如果 Block
是在棧上,將不會對 auto
變量產生強引用。
如果 Block
被拷貝到堆上,會調用 Block
內部的 copy
函數,copy
函數內部會調用 _Block_object_assign
函數,_Block_object_assign
函數會根據 auto
變量的修飾符(__strong
、__weak
、__unsafe_unretained
)做出相應的操作,形成強引用或者弱引用。
如果 Block
從堆上移除,會調用 Block
內部的 dispose
函數,dispose
函數內部會調用 _Block_object_dispose
函數。_Block_object_dispose
函數會自動釋放引用的 auto
變量(release
)。
如果想在 Block
中,對捕獲的對象的指針指向進行修改,則需要添加 __block
關鍵字:
int main(int argc, const char * argv[]) { @autoreleasepool { __block NSArray *array = [NSArray array]; void(^block)(void) = ^{ array = [NSArray array]; NSLog(@"%@", array); }; block(); } return 0; }
通過轉譯我們可以看出,跟 __block
修飾的標量類型相似,同樣會生成 __Block_byref_array_0
結構體來捕獲對象類型。同時其內部生成了 __Block_byref_id_object_copy
和 __Block_byref_id_object_dispose
兩個函數指針,用于對被結構體包裝的對象進行內存管理。
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; }
當 block
在棧上時,對它們都不會產生強引用。
當 block
拷貝到堆上時,都會通過 copy
函數來處理它們,__block
變量(假設變量名叫做 a
):
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
對象類型的 auto
變量(假設變量名叫做 p
):
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
當 block
從堆上移除時,都會通過 dispose
函數來釋放它們,__block
變量(假設變量名叫做 a
):
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
對象類型的 auto
變量(假設變量名叫做 p
):
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
“Objective C中Block怎么捕獲外部值”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。