前言
Blocks是C語言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這個新功能“Blocks”。從那開始,Block就出現(xiàn)在iOS和Mac系統(tǒng)各個API中,并被大家廣泛使用。一句話來形容Blocks,帶有自動變量(局部變量)的匿名函數(shù)。
Block在OC中的實(shí)現(xiàn)如下:
struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */}; struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *);};
從結(jié)構(gòu)圖中很容易看到isa,所以O(shè)C處理Block是按照對象來處理的。在iOS中,isa常見的就是_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock這3種(另外只在GC環(huán)境下還有3種使用的_NSConcreteFinalizingBlock,_NSConcreteAutoBlock,_NSConcreteWeakBlockVariable,本文暫不談?wù)撨@3種,有興趣的看看官方文檔)
以上介紹是Block的簡要實(shí)現(xiàn),接下來我們來仔細(xì)研究一下Block的捕獲外部變量的特性以及__block的實(shí)現(xiàn)原理。
研究工具:clang
為了研究編譯器的實(shí)現(xiàn)原理,我們需要使用 clang 命令。clang 命令可以將 Objetive-C 的源碼改寫成 C / C++ 語言的,借此可以研究 block 中各個特性的源碼實(shí)現(xiàn)方式。該命令是
clang -rewrite-objc block.c
目錄
- 1.Block捕獲外部變量實(shí)質(zhì)
- 2.Block的copy和release
- 3.Block中__block實(shí)現(xiàn)原理
一.Block捕獲外部變量實(shí)質(zhì)
拿起我們的Block一起來捕捉外部變量吧。
說到外部變量,我們要先說一下C語言中變量有哪幾種。一般可以分為一下5種:
- 自動變量
- 函數(shù)參數(shù)
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
研究Block的捕獲外部變量就要除去函數(shù)參數(shù)這一項(xiàng),下面一一根據(jù)這4種變量類型的捕獲情況進(jìn)行分析。
我們先根據(jù)這4種類型
- 自動變量
- 靜態(tài)變量
- 靜態(tài)全局變量
- 全局變量
寫出Block測試代碼。
這里很快就出現(xiàn)了一個錯誤,提示說自動變量沒有加__block,由于__block有點(diǎn)復(fù)雜,我們先實(shí)驗(yàn)靜態(tài)變量,靜態(tài)全局變量,全局變量這3類。測試代碼如下:
#import <Foundation/Foundation.h> int global_i = 1; static int static_global_j = 2; int main(int argc, const char * argv[]) { static int static_k = 3; int val = 4; void (^myBlock)(void) = ^{ global_i ++; static_global_j ++; static_k ++; NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val); }; global_i ++; static_global_j ++; static_k ++; val ++; NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val); myBlock(); return 0;}
運(yùn)行結(jié)果
Block 外 global_i = 2,static_global_j = 3,static_k = 4,val = 5Block 中 global_i = 3,static_global_j = 4,static_k = 5,val = 4
這里就有2點(diǎn)需要弄清楚了
1.為什么在Block里面不加__bolck不允許更改變量?
2.為什么自動變量的值沒有增加,而其他幾個變量的值是增加的?自動變量是什么狀態(tài)下被block捕獲進(jìn)去的?
為了弄清楚這2點(diǎn),我們用clang轉(zhuǎn)換一下源碼出來分析分析。
(main.m代碼行37行,文件大小832bype, 經(jīng)過clang轉(zhuǎn)換成main.cpp以后,代碼行數(shù)飆升至104810行,文件大小也變成了3.1MB)
源碼如下
int global_i = 1; static int static_global_j = 2; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *static_k; int val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *static_k = __cself->static_k; // bound by copy int val = __cself->val; // bound by copy global_i ++; static_global_j ++; (*static_k) ++; NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val); } 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[]) { static int static_k = 3; int val = 4; void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val)); global_i ++; static_global_j ++; static_k ++; val ++; NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val); ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); return 0;}
首先全局變量global_i和靜態(tài)全局變量static_global_j的值增加,以及它們被Block捕獲進(jìn)去,這一點(diǎn)很好理解,因?yàn)槭侨值?,作用域很廣,所以Block捕獲了它們進(jìn)去之后,在Block里面進(jìn)行++操作,Block結(jié)束之后,它們的值依舊可以得以保存下來。
接下來仔細(xì)看看自動變量和靜態(tài)變量的問題。
在__main_block_impl_0中,可以看到靜態(tài)變量static_k和自動變量val,被Block從外面捕獲進(jìn)來,成為__main_block_impl_0這個結(jié)構(gòu)體的成員變量了。
接著看構(gòu)造函數(shù),
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val)
這個構(gòu)造函數(shù)中,自動變量和靜態(tài)變量被捕獲為成員變量追加到了構(gòu)造函數(shù)中。
main里面的myBlock閉包中的__main_block_impl_0結(jié)構(gòu)體,初始化如下
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val)); impl.isa = &_NSConcreteStackBlock;impl.Flags = 0;impl.FuncPtr = __main_block_impl_0; Desc = &__main_block_desc_0_DATA;*_static_k = 4;val = 4;
到此,__main_block_impl_0結(jié)構(gòu)體就是這樣把自動變量捕獲進(jìn)來的。也就是說,在執(zhí)行Block語法的時候,Block語法表達(dá)式所使用的自動變量的值是被保存進(jìn)了Block的結(jié)構(gòu)體實(shí)例中,也就是Block自身中。
這里值得說明的一點(diǎn)是,如果Block外面還有很多自動變量,靜態(tài)變量,等等,這些變量在Block里面并不會被使用到。那么這些變量并不會被Block捕獲進(jìn)來,也就是說并不會在構(gòu)造函數(shù)里面?zhèn)魅胨鼈兊闹怠?/p>
Block捕獲外部變量僅僅只捕獲Block閉包里面會用到的值,其他用不到的值,它并不會去捕獲。
再研究一下源碼,我們注意到__main_block_func_0這個函數(shù)的實(shí)現(xiàn)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *static_k = __cself->static_k; // bound by copy int val = __cself->val; // bound by copy global_i ++; static_global_j ++; (*static_k) ++; NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val); }
我們可以發(fā)現(xiàn),系統(tǒng)自動給我們加上的注釋,bound by copy,自動變量val雖然被捕獲進(jìn)來了,但是是用 __cself->val來訪問的。Block僅僅捕獲了val的值,并沒有捕獲val的內(nèi)存地址。所以在__main_block_func_0這個函數(shù)中即使我們重寫這個自動變量val的值,依舊沒法去改變Block外面自動變量val的值。
OC可能是基于這一點(diǎn),在編譯的層面就防止開發(fā)者可能犯的錯誤,因?yàn)樽詣幼兞繘]法在Block中改變外部變量的值,所以編譯過程中就報編譯錯誤。錯誤就是最開始的那張截圖。
Variable is not assignable(missing __block type specifier)
小結(jié)一下:
到此為止,上面提出的第二個問題就解開答案了。自動變量是以值傳遞方式傳遞到Block的構(gòu)造函數(shù)里面去的。Block只捕獲Block中會用到的變量。由于只捕獲了自動變量的值,并非內(nèi)存地址,所以Block內(nèi)部不能改變自動變量的值。Block捕獲的外部變量可以改變值的是靜態(tài)變量,靜態(tài)全局變量,全局變量。上面例子也都證明過了。
剩下問題一我們還沒有解決。
回到上面的例子上面來,4種變量里面只有靜態(tài)變量,靜態(tài)全局變量,全局變量這3種是可以在Block里面被改變值的。仔細(xì)觀看源碼,我們能看出這3個變量可以改變值的原因。
-
靜態(tài)全局變量,全局變量由于作用域的原因,于是可以直接在Block里面被改變。他們也都存儲在全局區(qū)。
image 靜態(tài)變量傳遞給Block是內(nèi)存地址值,所以能在Block里面直接改變值。
根據(jù)官方文檔我們可以了解到,蘋果要求我們在自動變量前加入 __block關(guān)鍵字(__block storage-class-specifier存儲域類說明符),就可以在Block里面改變外部自動變量的值了。
總結(jié)一下在Block中改變變量值有2種方式,一是傳遞內(nèi)存地址指針到Block中,二是改變存儲區(qū)方式(__block)。
先來實(shí)驗(yàn)一下第一種方式,傳遞內(nèi)存地址到Block中,改變變量的值。
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { NSMutableString * str = [[NSMutableString alloc]initWithString:@"Hello,"]; void (^myBlock)(void) = ^{ [str appendString:@"World!"]; NSLog(@"Block中 str = %@",str); }; NSLog(@"Block外 str = %@",str); myBlock(); return 0;}
控制臺輸出:
Block 外 str = Hello,Block 中 str = Hello,World!
看結(jié)果是成功改變了變量的值了,轉(zhuǎn)換一下源碼。
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSMutableString *str; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSMutableString *str = __cself->str; // bound by copy ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_1); NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_2,str); }static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 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[]) { NSMutableString * str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_0); void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344)); NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_3,str); ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); return 0;}
在__main_block_func_0里面可以看到傳遞的是指針。所以成功改變了變量的值。
至于源碼里面的copy和dispose下一節(jié)會講到。
改變外部變量值的第二種方式是加 __block這個放在第三章里面討論,接下來我們先討論一下Block的copy的問題,因?yàn)檫@個問題會關(guān)系到 __block存儲域的問題。
二.Block的copy和dispose
OC中,一般Block就分為以下3種,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock。
先來說明一下3者的區(qū)別。
1.從捕獲外部變量的角度上來看
_NSConcreteStackBlock:
只用到外部局部變量、成員屬性變量,且沒有強(qiáng)指針引用的block都是StackBlock。
StackBlock的生命周期由系統(tǒng)控制的,一旦返回之后,就被系統(tǒng)銷毀了。_NSConcreteMallocBlock:
有強(qiáng)指針引用或copy修飾的成員屬性引用的block會被復(fù)制一份到堆中成為MallocBlock,沒有強(qiáng)指針引用即銷毀,生命周期由程序員控制_NSConcreteGlobalBlock:
沒有用到外界變量或只用到全局變量、靜態(tài)變量的block為_NSConcreteGlobalBlock,生命周期從創(chuàng)建到應(yīng)用程序結(jié)束。
沒有用到外部變量肯定是_NSConcreteGlobalBlock,這點(diǎn)很好理解。不過只用到全局變量、靜態(tài)變量的block也是_NSConcreteGlobalBlock。舉例如下:
#import <Foundation/Foundation.h> int global_i = 1;static int static_global_j = 2; int main(int argc, const char * argv[]) { static int static_k = 3; void (^myBlock)(void) = ^{ NSLog(@"Block中 變量 = %d %d %d",static_global_j ,static_k, global_i); }; NSLog(@"%@",myBlock); myBlock(); return 0;}
輸出:
<__NSGlobalBlock__: 0x100001050>Block中 變量 = 2 3 1
可見,只用到全局變量、靜態(tài)變量的block也可以是_NSConcreteGlobalBlock。
所以在ARC環(huán)境下,3種類型都可以捕獲外部變量。
2.從持有對象的角度上來看:
- _NSConcreteStackBlock是不持有對象的。
//以下是在MRC下執(zhí)行的 NSObject * obj = [[NSObject alloc]init]; NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount); void (^myBlock)(void) = ^{ NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount); }; NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount); myBlock();
輸出:
1.Block外 obj = 12.Block外 obj = 1Block中 obj = 1
- _NSConcreteMallocBlock是持有對象的。
//以下是在MRC下執(zhí)行的 NSObject * obj = [[NSObject alloc]init]; NSLog(@"1.Block外 obj = %lu",(unsigned long)obj.retainCount); void (^myBlock)(void) = [^{ NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount); }copy]; NSLog(@"2.Block外 obj = %lu",(unsigned long)obj.retainCount); myBlock(); [myBlock release]; NSLog(@"3.Block外 obj = %lu",(unsigned long)obj.retainCount);
輸出:
1.Block外 obj = 12.Block外 obj = 2Block中 obj = 23.Block外 obj = 1
- _NSConcreteGlobalBlock也不持有對象
//以下是在MRC下執(zhí)行的 void (^myBlock)(void) = ^{ NSObject * obj = [[NSObject alloc]init]; NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount); }; myBlock();
輸出:
Block 中 obj = 1
由于_NSConcreteStackBlock所屬的變量域一旦結(jié)束,那么該Block就會被銷毀。在ARC環(huán)境下,編譯器會自動的判斷,把Block自動的從棧copy到堆。比如當(dāng)Block作為函數(shù)返回值的時候,肯定會copy到堆上。
1.手動調(diào)用copy
2.Block是函數(shù)的返回值
3.Block被強(qiáng)引用,Block被賦值給__strong或者id類型
4.調(diào)用系統(tǒng)API入?yún)⒅泻衭singBlcok的方法
以上4種情況,系統(tǒng)都會默認(rèn)調(diào)用copy方法把Block賦復(fù)制
但是當(dāng)Block為函數(shù)參數(shù)的時候,就需要我們手動的copy一份到堆上了。這里除去系統(tǒng)的API我們不需要管,比如GCD等方法中本身帶usingBlock的方法,其他我們自定義的方法傳遞Block為參數(shù)的時候都需要手動copy一份到堆上。
copy函數(shù)把Block從棧上拷貝到堆上,dispose函數(shù)是把堆上的函數(shù)在廢棄的時候銷毀掉。
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))#define Block_release(...) _Block_release((const void *)(__VA_ARGS__)) // Create a heap based copy of a Block or simply add a reference to an existing one.// This must be paired with Block_release to recover memory, even when running// under Objective-C Garbage Collection.BLOCK_EXPORT void *_Block_copy(const void *aBlock) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2); // Lose the reference, and if heap based and last reference, recover the memoryBLOCK_EXPORT void _Block_release(const void *aBlock) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2); // Used by the compiler. Do not call this function yourself.BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2); // Used by the compiler. Do not call this function yourself.BLOCK_EXPORT void _Block_object_dispose(const void *, const int) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
上面是源碼中2個常用的宏定義和4個常用的方法,一會我們就會看到這4個方法。
static void *_Block_copy_internal(const void *arg, const int flags) { struct Block_layout *aBlock; const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; // 1 if (!arg) return NULL; // 2 aBlock = (struct Block_layout *)arg; // 3 if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; } // 4 else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; } // 5 struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return (void *)0; // 6 memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // 7 result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 1; // 8 result->isa = _NSConcreteMallocBlock; // 9 if (result->flags & BLOCK_HAS_COPY_DISPOSE) { (*aBlock->descriptor->copy)(result, aBlock); // do fixup } return result;}
上面這一段是Block_copy的一個實(shí)現(xiàn),實(shí)現(xiàn)了從_NSConcreteStackBlock復(fù)制到_NSConcreteMallocBlock的過程。對應(yīng)有9個步驟。
void _Block_release(void *arg) { // 1 struct Block_layout *aBlock = (struct Block_layout *)arg; if (!aBlock) return; // 2 int32_t newCount; newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK; // 3 if (newCount > 0) return; // 4 if (aBlock->flags & BLOCK_NEEDS_FREE) { if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock); _Block_deallocator(aBlock); } // 5 else if (aBlock->flags & BLOCK_IS_GLOBAL) { ; } // 6 else { printf("Block_release called upon a stack Block: %p, ignored\\\\n", (void *)aBlock); }}
上面這一段是Block_release的一個實(shí)現(xiàn),實(shí)現(xiàn)了怎么釋放一個Block。對應(yīng)有6個步驟。
上述2個方法的詳細(xì)解析可以看這篇文章
回到上一章節(jié)中最后的例子,字符串的例子中來,轉(zhuǎn)換源碼之后,我們會發(fā)現(xiàn)多了一個copy和dispose方法。
因?yàn)樵贑語言的結(jié)構(gòu)體中,編譯器沒法很好的進(jìn)行初始化和銷毀操作。這樣對內(nèi)存管理來說是很不方便的。所以就在 __main_block_desc_0結(jié)構(gòu)體中間增加成員變量 void (copy)(struct __main_block_impl_0, struct __main_block_impl_0)和void (dispose)(struct __main_block_impl_0*),利用OC的Runtime進(jìn)行內(nèi)存管理。
相應(yīng)的增加了2個方法。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
這里的_Block_object_assign和_Block_object_dispose就對應(yīng)著retain和release方法。
BLOCK_FIELD_IS_OBJECT 是Block截獲對象時候的特殊標(biāo)示,如果是截獲的__block,那么是BLOCK_FIELD_IS_BYREF。
三.Block中__block實(shí)現(xiàn)原理
我們繼續(xù)研究一下__block實(shí)現(xiàn)原理。
1.普通非對象的變量
先來看看普通變量的情況。
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { __block int i = 0; void (^myBlock)(void) = ^{ i ++; NSLog(@"%d",i); }; myBlock(); return 0;}
把上述代碼用clang轉(zhuǎn)換成源碼。
struct __Block_byref_i_0 { void *__isa;__Block_byref_i_0 *__forwarding; int __flags; int __size; int i;}; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_i_0 *i; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref (i->__forwarding->i) ++; NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i)); }static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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[]) { __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0}; void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344)); ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); return 0;}
從源碼我們能發(fā)現(xiàn),帶有 __block的變量也被轉(zhuǎn)化成了一個結(jié)構(gòu)體__Block_byref_i_0,這個結(jié)構(gòu)體有5個成員變量。第一個是isa指針,第二個是指向自身類型的__forwarding指針,第三個是一個標(biāo)記flag,第四個是它的大小,第五個是變量值,名字和變量名同名。
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
源碼中是這樣初始化的。__forwarding指針初始化傳遞的是自己的地址。然而這里__forwarding指針真的永遠(yuǎn)指向自己么?我們來做一個實(shí)驗(yàn)。
//以下代碼在MRC中運(yùn)行 __block int i = 0; NSLog(@"%p",&i); void (^myBlock)(void) = [^{ i ++; NSLog(@"這是Block 里面%p",&i); }copy];
我們把Block拷貝到了堆上,這個時候打印出來的2個i變量的地址就不同了。
0x7fff5fbff818<__NSMallocBlock__: 0x100203cc0>這是Block 里面 0x1002038a8
地址不同就可以很明顯的說明__forwarding指針并沒有指向之前的自己了。那__forwarding指針現(xiàn)在指向到哪里了呢?
Block里面的__block的地址和Block的地址就相差1052。我們可以很大膽的猜想,__block現(xiàn)在也在堆上了。
出現(xiàn)這個不同的原因在于這里把Block拷貝到了堆上。
由第二章里面詳細(xì)分析的,堆上的Block會持有對象。我們把Block通過copy到了堆上,堆上也會重新復(fù)制一份Block,并且該Block也會繼續(xù)持有該__block。當(dāng)Block釋放的時候,__block沒有被任何對象引用,也會被釋放銷毀。
__forwarding指針這里的作用就是針對堆的Block,把原來__forwarding指針指向自己,換成指向_NSConcreteMallocBlock上復(fù)制之后的__block自己。然后堆上的變量的__forwarding再指向自己。這樣不管__block怎么復(fù)制到堆上,還是在棧上,都可以通過(i->__forwarding->i)來訪問到變量值。
所以在__main_block_func_0函數(shù)里面就是寫的(i->__forwarding->i)。
這里還有一個需要注意的地方。還是從例子說起:
//以下代碼在MRC中運(yùn)行 __block int i = 0; NSLog(@"%p",&i); void (^myBlock)(void) = ^{ i ++; NSLog(@"Block 里面的%p",&i); }; NSLog(@"%@",myBlock); myBlock();
結(jié)果和之前copy的例子完全不同。
0x7fff5fbff818<__NSStackBlock__: 0x7fff5fbff7c0>** 0x7fff5fbff818
Block在捕獲住__block變量之后,并不會復(fù)制到堆上,所以地址也一直都在棧上。這與ARC環(huán)境下的不一樣。
ARC環(huán)境下,不管有沒有copy,__block都會變copy到堆上,Block也是__NSMallocBlock。
感謝@酷酷的哀殿 指出錯誤,感謝@bestswifter 指點(diǎn)。上述說法有點(diǎn)不妥,詳細(xì)見文章末尾更新。
ARC環(huán)境下,一旦Block賦值就會觸發(fā)copy,__block就會copy到堆上,Block也是__NSMallocBlock。ARC環(huán)境下也是存在__NSStackBlock的時候,這種情況下,__block就在棧上。
MRC環(huán)境下,只有copy,__block才會被復(fù)制到堆上,否則,__block一直都在棧上,block也只是__NSStackBlock,這個時候__forwarding指針就只指向自己了。
至此,文章開頭提出的問題一,也解答了。__block的實(shí)現(xiàn)原理也已經(jīng)明了。
2.對象的變量
還是先舉一個例子:
//以下代碼是在ARC下執(zhí)行的#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { __block id block_obj = [[NSObject alloc]init]; id obj = [[NSObject alloc]init]; NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj); void (^myBlock)(void) = ^{ NSLog(@"***Block中****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj); }; myBlock(); return 0;}
輸出
block_obj = [<NSObject: 0x100b027d0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b03b50> , 0x7fff5fbff7b8]Block****中********block_obj = [<NSObject: 0x100b027d0> , 0x100f000a8] , obj = [<NSObject: 0x100b03b50> , 0x100f00070]
我們把上面的代碼轉(zhuǎn)換成源碼研究一下:
struct __Block_byref_block_obj_0 { void *__isa;__Block_byref_block_obj_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); id block_obj;}; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id obj; __Block_byref_block_obj_0 *block_obj; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__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_block_obj_0 *block_obj = __cself->block_obj; // bound by ref id obj = __cself->obj; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , obj , &obj); }static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 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[]) { __attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))}; id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , obj , &obj); void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344)); ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); return 0;}
首先需要說明的一點(diǎn)是對象在OC中,默認(rèn)聲明自帶__strong所有權(quán)修飾符的,所以main開頭我們聲明的
__block id block_obj = [[NSObject alloc]init];id obj = [[NSObject alloc]init];
等價于
__block id __strong block_obj = [[NSObject alloc]init];id __strong obj = [[NSObject alloc]init];
在轉(zhuǎn)換出來的源碼中,我們也可以看到,Block捕獲了__block,并且強(qiáng)引用了,因?yàn)樵赺_Block_byref_block_obj_0結(jié)構(gòu)體中,有一個變量是id block_obj,這個默認(rèn)也是帶__strong所有權(quán)修飾符的。
根據(jù)打印出來的結(jié)果來看,ARC環(huán)境下,Block捕獲外部對象變量,是都會copy一份的,地址都不同。只不過帶有__block修飾符的變量會被捕獲到Block內(nèi)部持有。
我們再來看看MRC環(huán)境下的情況,還是將上述代碼的例子運(yùn)行在MRC中。
輸出:
block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff7b8]Block****中********block_obj = [<NSObject: 0x100b001b0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b001c0> , 0x7fff5fbff790]
這個時候block在棧上,NSStackBlock,可以打印出來retainCount值都是1。當(dāng)把這個block copy一下,就變成NSMallocBlock,對象的retainCount值就會變成2了。
總結(jié):
在MRC環(huán)境下,__block根本不會對指針?biāo)赶虻膶ο髨?zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制。
而在ARC環(huán)境下,對于聲明為__block的外部對象,在block內(nèi)部會進(jìn)行retain,以至于在block環(huán)境內(nèi)能安全的引用外部對象,所以才會產(chǎn)生循環(huán)引用的問題!
最后
關(guān)于Block捕獲外部變量有很多用途,用途也很廣,只有弄清了捕獲變量和持有的變量的概念以后,之后才能清楚的解決Block循環(huán)引用的問題。
再次回到文章開頭,5種變量,自動變量,函數(shù)參數(shù) ,靜態(tài)變量,靜態(tài)全局變量,全局變量,如果嚴(yán)格的來說,捕獲是必須在Block結(jié)構(gòu)體__main_block_impl_0里面有成員變量的話,Block能捕獲的變量就只有帶有自動變量和靜態(tài)變量了。捕獲進(jìn)Block的對象會被Block持有。
對于非對象的變量來說,
自動變量的值,被copy進(jìn)了Block,不帶__block的自動變量只能在里面被訪問,并不能改變值。
帶__block的自動變量 和 靜態(tài)變量 就是直接地址訪問。所以在Block里面可以直接改變變量的值。
而剩下的靜態(tài)全局變量,全局變量,函數(shù)參數(shù),也是可以在直接在Block中改變變量值的,但是他們并沒有變成Block結(jié)構(gòu)體__main_block_impl_0的成員變量,因?yàn)樗麄兊淖饔糜虼?,所以可以直接更改他們的值?/p>
值得注意的是,靜態(tài)全局變量,全局變量,函數(shù)參數(shù)他們并不會被Block持有,也就是說不會增加retainCount值。
對于對象來說,
在MRC環(huán)境下,__block根本不會對指針?biāo)赶虻膶ο髨?zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制。
而在ARC環(huán)境下,對于聲明為__block的外部對象,在block內(nèi)部會進(jìn)行retain,以至于在block環(huán)境內(nèi)能安全的引用外部對象。
請大家多多指點(diǎn)。
更新
在ARC環(huán)境下,Block也是存在__NSStackBlock的時候的,平時見到最多的是_NSConcreteMallocBlock,是因?yàn)槲覀儠lock有賦值操作,所以ARC下,block 類型通過=進(jìn)行傳遞時,會導(dǎo)致調(diào)用objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈。并導(dǎo)致 NSStackBlock 類型的 block 轉(zhuǎn)換為 NSMallocBlock 類型。
舉例如下:
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { __block int temp = 10; NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);}); return 0;}
輸出
<__NSStackBlock__: 0x7fff5fbff768>
這種情況就是ARC環(huán)境下Block是__NSStackBlock的類型。
--------------------- 本文來自 想名真難 的CSDN 博客 ,全文地址請點(diǎn)擊:https://blog.csdn.net/u014600626/article/details/78697535?utm_source=copy