<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
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
物件本身分為三種型別:
auto
變數,呼叫 copy
方法之後不會發生變化。auto
變數,呼叫 copy
方法之後儲存位置從棧變為堆。__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如何捕獲外部值的詳細內容,更多關於Objective C Block捕獲外部值的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45