Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這個新功能「Blocks」。從那開始,Block就出如今iOS和Mac系統各個API中,並被你們普遍使用。一句話來形容Blocks,帶有自動變量(局部變量)的匿名函數。html
Block在OC中的實現以下:ios
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 *);
};複製代碼
從結構圖中很容易看到isa,因此OC處理Block是按照對象來處理的。在iOS中,isa常見的就是_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock這3種(另外只在GC環境下還有3種使用的_NSConcreteFinalizingBlock,_NSConcreteAutoBlock,_NSConcreteWeakBlockVariable,本文暫不談論這3種,有興趣的看看官方文檔)程序員
以上介紹是Block的簡要實現,接下來咱們來仔細研究一下Block的捕獲外部變量的特性以及__block的實現原理。swift
研究工具:clang 爲了研究編譯器的實現原理,咱們須要使用 clang 命令。clang 命令能夠將 Objetive-C 的源碼改寫成 C / C++ 語言的,藉此能夠研究 block 中各個特性的源碼實現方式。該命令是vim
clang -rewrite-objc block.c複製代碼
拿起咱們的Block一塊兒來捕捉外部變量吧。閉包
說到外部變量,咱們要先說一下C語言中變量有哪幾種。通常能夠分爲一下5種:app
研究Block的捕獲外部變量就要除去函數參數這一項,下面一一根據這4種變量類型的捕獲狀況進行分析。ide
咱們先根據這4種類型函數
寫出Block測試代碼。工具
這裏很快就出現了一個錯誤,提示說自動變量沒有加__block,因爲__block有點複雜,咱們先實驗靜態變量,靜態全局變量,全局變量這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;
}複製代碼
運行結果
Block 外 global_i = 2,static_global_j = 3,static_k = 4,val = 5
Block 中 global_i = 3,static_global_j = 4,static_k = 5,val = 4複製代碼
這裏就有2點須要弄清楚了 1.爲何在Block裏面不加__bolck不容許更改變量? 2.爲何自動變量的值沒有增長,而其餘幾個變量的值是增長的?自動變量是什麼狀態下被block捕獲進去的?
爲了弄清楚這2點,咱們用clang轉換一下源碼出來分析分析。
(main.m代碼行37行,文件大小832bype, 通過clang轉換成main.cpp之後,代碼行數飆升至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和靜態全局變量static_global_j的值增長,以及它們被Block捕獲進去,這一點很好理解,由於是全局的,做用域很廣,因此Block捕獲了它們進去以後,在Block裏面進行++操做,Block結束以後,它們的值依舊能夠得以保存下來。
接下來仔細看看自動變量和靜態變量的問題。 在__main_block_impl_0中,能夠看到靜態變量static_k和自動變量val,被Block從外面捕獲進來,成爲__main_block_impl_0這個結構體的成員變量了。
接着看構造函數,
__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)複製代碼
這個構造函數中,自動變量和靜態變量被捕獲爲成員變量追加到了構造函數中。
main裏面的myBlock閉包中的__main_block_impl_0結構體,初始化以下
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結構體就是這樣把自動變量捕獲進來的。也就是說,在執行Block語法的時候,Block語法表達式所使用的自動變量的值是被保存進了Block的結構體實例中,也就是Block自身中。
這裏值得說明的一點是,若是Block外面還有不少自動變量,靜態變量,等等,這些變量在Block裏面並不會被使用到。那麼這些變量並不會被Block捕獲進來,也就是說並不會在構造函數裏面傳入它們的值。
Block捕獲外部變量僅僅只捕獲Block閉包裏面會用到的值,其餘用不到的值,它並不會去捕獲。
再研究一下源碼,咱們注意到__main_block_func_0這個函數的實現
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);
}複製代碼
咱們能夠發現,系統自動給咱們加上的註釋,bound by copy,自動變量val雖然被捕獲進來了,可是是用 __cself->val來訪問的。Block僅僅捕獲了val的值,並無捕獲val的內存地址。因此在__main_block_func_0這個函數中即便咱們重寫這個自動變量val的值,依舊無法去改變Block外面自動變量val的值。
OC多是基於這一點,在編譯的層面就防止開發者可能犯的錯誤,由於自動變量無法在Block中改變外部變量的值,因此編譯過程當中就報編譯錯誤。錯誤就是最開始的那張截圖。
Variable is not assignable(missing __block type specifier)複製代碼
小結一下: 到此爲止,上面提出的第二個問題就解開答案了。自動變量是以值傳遞方式傳遞到Block的構造函數裏面去的。Block只捕獲Block中會用到的變量。因爲只捕獲了自動變量的值,並內存地址,因此Block內部不能改變自動變量的值。Block捕獲的外部變量能夠改變值的是靜態變量,靜態全局變量,全局變量。上面例子也都證實過了。
剩下問題一咱們尚未解決。
回到上面的例子上面來,4種變量裏面只有靜態變量,靜態全局變量,全局變量這3種是能夠在Block裏面被改變值的。仔細觀看源碼,咱們能看出這3個變量能夠改變值的緣由。
靜態全局變量,全局變量因爲做用域的緣由,因而能夠直接在Block裏面被改變。他們也都存儲在全局區。
靜態變量傳遞給Block是內存地址值,因此能在Block裏面直接改變值。
根據官方文檔咱們能夠了解到,蘋果要求咱們在自動變量前加入 __block關鍵字(__block storage-class-specifier存儲域類說明符),就能夠在Block裏面改變外部自動變量的值了。
總結一下在Block中改變變量值有2種方式,一是傳遞內存地址指針到Block中,二是改變存儲區方式(__block)。
先來實驗一下第一種方式,傳遞內存地址到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!複製代碼
看結果是成功改變了變量的值了,轉換一下源碼。
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下一節會講到。
改變外部變量值的第二種方式是加 __block這個放在第三章裏面討論,接下來咱們先討論一下Block的copy的問題,由於這個問題會關係到 __block存儲域的問題。
OC中,通常Block就分爲如下3種,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock。
先來講明一下3者的區別。
_NSConcreteStackBlock: 只用到外部局部變量、成員屬性變量,且沒有強指針引用的block都是StackBlock。 StackBlock的生命週期由系統控制的,一旦返回以後,就被系統銷燬了。
_NSConcreteMallocBlock: 有強指針引用或copy修飾的成員屬性引用的block會被複制一份到堆中成爲MallocBlock,沒有強指針引用即銷燬,生命週期由程序員控制
_NSConcreteGlobalBlock: 沒有用到外界變量或只用到全局變量、靜態變量的block爲_NSConcreteGlobalBlock,生命週期從建立到應用程序結束。
沒有用到外部變量確定是_NSConcreteGlobalBlock,這點很好理解。不過只用到全局變量、靜態變量的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複製代碼
可見,只用到全局變量、靜態變量的block也能夠是_NSConcreteGlobalBlock。
因此在ARC環境下,3種類型均可以捕獲外部變量。
//如下是在MRC下執行的
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 = 1
2.Block外 obj = 1
Block中 obj = 1複製代碼
//如下是在MRC下執行的
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 = 1
2.Block外 obj = 2
Block中 obj = 2
3.Block外 obj = 1複製代碼
//如下是在MRC下執行的
void (^myBlock)(void) = ^{
NSObject * obj = [[NSObject alloc]init];
NSLog(@"Block中 obj = %lu",(unsigned long)obj.retainCount);
};
myBlock();複製代碼
輸出:
Block 中 obj = 1複製代碼
因爲_NSConcreteStackBlock所屬的變量域一旦結束,那麼該Block就會被銷燬。在ARC環境下,編譯器會自動的判斷,把Block自動的從棧copy到堆。好比當Block做爲函數返回值的時候,確定會copy到堆上。
1.手動調用copy 2.Block是函數的返回值 3.Block被強引用,Block被賦值給__strong或者id類型 4.調用系統API入參中含有usingBlcok的方法
以上4種狀況,系統都會默認調用copy方法把Block賦複製
可是當Block爲函數參數的時候,就須要咱們手動的copy一份到堆上了。這裏除去系統的API咱們不須要管,好比GCD等方法中自己帶usingBlock的方法,其餘咱們自定義的方法傳遞Block爲參數的時候都須要手動copy一份到堆上。
copy函數把Block從棧上拷貝到堆上,dispose函數是把堆上的函數在廢棄的時候銷燬掉。
#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 memory
BLOCK_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的一個實現,實現了從_NSConcreteStackBlock複製到_NSConcreteMallocBlock的過程。對應有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的一個實現,實現了怎麼釋放一個Block。對應有6個步驟。
上述2個方法的詳細解析能夠看這篇文章
回到上一章節中最後的例子,字符串的例子中來,轉換源碼以後,咱們會發現多了一個copy和dispose方法。
由於在C語言的結構體中,編譯器無法很好的進行初始化和銷燬操做。這樣對內存管理來講是很不方便的。因此就在 __main_block_desc_0結構體中間增長成員變量 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*)和void (*dispose)(struct __main_block_impl_0*),利用OC的Runtime進行內存管理。
相應的增長了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就對應着retain和release方法。
BLOCK_FIELD_IS_OBJECT 是Block截獲對象時候的特殊標示,若是是截獲的__block,那麼是BLOCK_FIELD_IS_BYREF。
咱們繼續研究一下__block實現原理。
#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轉換成源碼。
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;
}複製代碼
從源碼咱們能發現,帶有 __block的變量也被轉化成了一個結構體__Block_byref_i_0,這個結構體有5個成員變量。第一個是isa指針,第二個是指向自身類型的__forwarding指針,第三個是一個標記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指針真的永遠指向本身麼?咱們來作一個實驗。
//如下代碼在MRC中運行
__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指針如今指向到哪裏了呢?
Block裏面的__block的地址和Block的地址就相差1052。咱們能夠很大膽的猜測,__block如今也在堆上了。
出現這個不一樣的緣由在於這裏把Block拷貝到了堆上。
由第二章裏面詳細分析的,堆上的Block會持有對象。咱們把Block經過copy到了堆上,堆上也會從新複製一份Block,而且該Block也會繼續持有該__block。當Block釋放的時候,__block沒有被任何對象引用,也會被釋放銷燬。
__forwarding指針這裏的做用就是針對堆的Block,把原來__forwarding指針指向本身,換成指向_NSConcreteMallocBlock上覆制以後的__block本身。而後堆上的變量的__forwarding再指向本身。這樣無論__block怎麼複製到堆上,仍是在棧上,均可以經過(i->__forwarding->i)來訪問到變量值。
因此在__main_block_func_0函數裏面就是寫的(i->__forwarding->i)。
這裏還有一個須要注意的地方。仍是從例子提及:
//如下代碼在MRC中運行
__block int i = 0;
NSLog(@"%p",&i);
void (^myBlock)(void) = ^{
i ++;
NSLog(@"Block 裏面的%p",&i);
};
NSLog(@"%@",myBlock);
myBlock();複製代碼
結果和以前copy的例子徹底不一樣。
0x7fff5fbff818
<__NSStackBlock__: 0x7fff5fbff7c0>**
0x7fff5fbff818複製代碼
Block在捕獲住__block變量以後,並不會複製到堆上,因此地址也一直都在棧上。這與ARC環境下的不同。
ARC環境下,無論有沒有copy,__block都會變copy到堆上,Block也是__NSMallocBlock。
感謝@酷酷的哀殿 指出錯誤,感謝@bestswifter 指點。上述說法有點不妥,詳細見文章末尾更新。
ARC環境下,一旦Block賦值就會觸發copy,__block就會copy到堆上,Block也是__NSMallocBlock。ARC環境下也是存在__NSStackBlock的時候,這種狀況下,__block就在棧上。
MRC環境下,只有copy,__block纔會被複制到堆上,不然,__block一直都在棧上,block也只是NSStackBlock,這個時候\forwarding指針就只指向本身了。
至此,文章開頭提出的問題一,也解答了。__block的實現原理也已經明瞭。
關於Block捕獲外部變量有不少用途,用途也很廣,只有弄清了捕獲變量和持有的變量的概念之後,以後才能清楚的解決Block循環引用的問題。
再次回到文章開頭,5種變量,自動變量,函數參數 ,靜態變量,靜態全局變量,全局變量,若是嚴格的來講,捕獲是必須在Block結構體__main_block_impl_0裏面有成員變量的話,Block能捕獲的變量就只有帶有自動變量和靜態變量了。捕獲進Block的對象會被Block持有。
自動變量的值,被copy進了Block,不帶__block的自動變量只能在裏面被訪問,並不能改變值。
帶__block的自動變量 和 靜態變量 就是直接地址訪問。因此在Block裏面能夠直接改變變量的值。
而剩下的靜態全局變量,全局變量,函數參數,也是能夠在直接在Block中改變變量值的,可是他們並無變成Block結構體__main_block_impl_0的成員變量,由於他們的做用域大,因此能夠直接更改他們的值。
值得注意的是,靜態全局變量,全局變量,函數參數他們並不會被Block持有,也就是說不會增長retainCount值。
請你們多多指點。
更新
在ARC環境下,Block也是存在__NSStackBlock的時候的,平時見到最多的是_NSConcreteMallocBlock,是由於咱們會對Block有賦值操做,因此ARC下,block 類型經過=進行傳遞時,會致使調用objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈。並致使 __NSStackBlock__ 類型的 block 轉換爲 __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環境下Block是__NSStackBlock的類型。