全乎的Blocks講解
一句話概括Blocks:<strong>帶有自動變量(局部變量)的匿名函數</strong>
Blocks模式
Block語法
Block的書寫形式:<strong>^ 返回類型 參數列表 表達式</strong>
^int (int count) {
return count + 1;
}
我們可以看到Block的語法和C語言函數相比有兩點不同
- 沒有函數名
- 帶有“^”
省略形式:
1、返回類型。省略返回類型時,如果表達式中有return語句就使用該返回類型,如果表達式沒有return語句就是用void類型。表達式中含有多個return語句時,所有return的返回類型必須相同。
2、參數列表。如果使用參數,參數列表也可以忽略。
綜上,最簡單的Block形式如下:
^{
printf("Blocks\n");
}
Blocks變量
(一)Block類型變量與一般的C語言變量完全相同,可作為一下用途使用:
1、自動變量
2、函數參數
3、靜態變量
4、靜態全局變量
5、全局變量
當在函數和返回值中使用Block類型變量是,記述方式極為復雜。此時,我們可以像使用函數指針類型那樣,使用typedef來解決問題。
typedef int (^blk_t)(int);
void func(int (^blk)(int))//原來的記述方式
void func(blk_t blk) //現在的記述方式
·
int (^func())//原來的記述方式
blk_t func() //現在的記述方式
(二)Block中變量值得獲取
Block表達式截獲所使用的自動變量的值,即保存自動變量的瞬間值。
(三)__block說明符
默認情況下,執行Block語法時,截獲的是自動變量的瞬間值,截獲保存之后就不能改寫這個值,如果嘗試修改這個值,編譯器會報編譯錯誤。
如果想在Block語法的表達式中將值賦給Block語法外聲明的自動變量,修改在該自動變量上附加__block說明符。
Blocks底層的實現
Block是“帶有自動變量的匿名函數”,實際上Block是作為極普通的c語言源代碼來處理的。我們先來看一下個最簡單的block語法:
int main()
{
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}
我們使用clang將objc代碼轉換為c++的源代碼。說是c++,其實也僅是使用了struct結構,本質還是c代碼。
//block變量結構體
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
}
...
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block\n");
}
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()
{
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
objc中短短的幾行代碼,轉換為c++代碼后,竟然增加了這么多。我們從轉換后的代碼中選取出最重要的這幾行代碼。下面我們就來分析一下轉換的代碼,看看block到底是怎么通過c++代碼來實現的。
很簡單的,第一眼我們就看到轉換后的代碼中有一行printf("block\n");
,沒錯,這就是objc代碼中的block塊內容。發現原來的Block函數,轉換成了一個c++函數:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block\n");
}
函數的名字是根據Block語法所屬的函數名和該Block語法在該函數出現的順序值來給Block函數命名。
此外,我們發現轉換的后函數有一個入參__cself
,這個參數就相當于c++實例方法中指向實例自身的變量this或者objc實例方法中指向對象自身的變量self,即參數__cself
為指向Block值得變量。我們先來看看這個參數的聲明:
struct __main_block_impl_0 * __cself
__cself
是 __main_block_impl_0
結構體的指針,該結構聲明如下:
//去除構造函數
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
__main_block_impl_0
結構體有兩個成員變量,一個__block_impl
結構體impl,一個 __main_block_desc_0
結構體指針Desc。一個一個的來看,
先看一下 __block_impl
這個結構體
struct __block_impl {
void *isa; //如同對象類型的Class isa,將Block作為Objc對象是,關于該對象類的信息放置于isa指向的對象中
int Flags; //某些標志
int Reserved; //保留區域
void *FuncPtr; //函數指針
}
我們來分一下這個結構體中的四個參數:
1、isa,我們首先要認識到Blocks在objc也是對象,在Objective-C中使用id這一類型來存儲對象。
typedef struct objc_object {
Class isa;
} *id;
objc_object是表示一個類的實例的結構體,id為objc_object結構體的指針類型。
該結構體只有一個字段,即指向 其類的isa指針。這樣,當我們向一個Objective-C對象發送消息時,運行時庫會根據實例對象的isa指針找到這個實例對象所屬的類。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對象的selector指向的方法。找到后即運行這個方法。當創建一個特定類的實例對象時,分配的內存包含一個objc_object數據結構,然后是類的實例變量的數據。
我們再來看看Class:
typedef struct objc_class *Class;
Class為objc_class結構體的指針類型。objc_class結構體的定義如下:
struct objc_class {
Class isa; //在Objective-c中,所有的類自身也是一個對象,這個對象的Class里面也有一個isa指針,指向metaClass(元類)
·
·
Class super_class; //指向該類的父類,如果該類已經是最頂層的根類,則super_class為NULL
}
在這里我們順便提一下元類的概念。上面介紹objc_class結構是,提到,所有的類自身也是一個對象,我們可以向這個對象發送消息。如:
NSArray *array = [NSArray array];
在這個例子中,+array消息發送給了NSArray類,而這個NAArray類也是一個對象。既然是對象,那么他也是一個Objc_object指針,他包含一個指向其類的一個isa指針。那么這些就有一個問題了,這個isa指針指向什么呢?為了調用+array方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念:<strong>meta-class是一個類對象的類</strong>.
當我們向一個對象發消息時,runtime會在這個對象所屬的這個類的方法列表中查找相應方法,而向一個類發送消息是,會在這個類的meta-calss元類的方法列表中查找。
ps:meta-class之所以重要,是因為它存儲著一個類的所有類方法。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同
實例-類-元類的關系如下圖:
</br>
回到正文,__main_block_impl_0結構就相當于objc_object結構體,其內部的isa指針目前一般初始化為
isa = &_NSConcreterStackBlock;
,即該Block類的信息放在了_NSConcreterStackBlock中。2、Flags 標志位,默認設為0
3、Reserved 保留區
4、FuncPtr 函數指針,將Block轉換后的函數指針賦值給FuncPtr,供以后調用。
接下來我們來看一下這個__main_block_desc_0
結構體,其聲明如下:
static struct __main_block_desc_0 {
size_t reserved; //保留區域
size_t Block_size; //Block的大小
};
說了這么多,我們來看一下objc是怎么把Block轉換成c++函數調用的。
首先我們構造過程:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
乍一看,這個函數比較復雜,轉換較多,我們分開來看:
struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __mian_block_impl_0 *blk = &tmp;
整個過程就是將__mian_block_impl_0
結構體類型的自我變量,即棧上生成的__mian_block_impl_0
結構體實例的指針,賦值給__mian_block_impl_0
結構體指針類型的變量blk。對應最初源代碼:
void (^blk)(void) = ^{printf("Block\n");};
下面就來看看__mian_block_impl_0結構體實例的構造函數:
__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)
第一個參數是由Block語法轉換后的C語言函數指針,第二個參數是作為靜態全局變量初始化的__main_block_desc_0
結構體實例指針。__main_block_desc_0
的初始化如下:
static struct __main_block_desc_0 __main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0) //__main_block_impl_0結構體實例的大小
};
那__mian_block_impl_0
結構體實例具體是如何初始化的呢,我們將__mian_block_impl_0
結構體中的成員變量展開,如下:
struct __mian_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0 * Desc;
}
初始化如下:
isa = &_NSConcreterStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
再來看一下調用過程:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉轉換部分:
(*blk->FuncPtr)(blk)
就是簡單地使用函數指針調用函數。Block語法轉換的__main_block_func_0
函數指針被賦值給成員變量FuncPtr。此外,我們也發現了__main_block_func_0
函數的參數__cself
指向Block值。
Block截獲自動變量值
上一節我們講解了Block語法的底層實現,這一節主要講解如何截取自動變量的值,前面曾提到過,Block獲取的是自動變量的瞬間值。和前面一樣,我們還使用clang對源代碼進行轉換。
源代碼如下:
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt,val);};
blk();
return 0;
}
轉換后的代碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), 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) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,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 dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
與上一節的代碼作比較,我們注意到,在__main_block_impl_0
結構體中增加兩個變量const char *fmt
、int val
,類型與自動變量的類型完全相同。而且,還發現Block語法表達式中沒有使用的自動變量不會被追加。即Blocks的自動變量截獲只針對Block中使用的自動變量。
接下來我們再看一下構造函數:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val);
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
可以發現,執行Block語法時,使用自動變量fmt和val來初始化__main_block_impl_0
結構體實例。
再來看一下執行函數:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,val);
}
再轉換后的函數中,使用的是自動變量保存在__main_block_impl_0
結構體實例中的值。由此也說明了,Block獲取的是自動變量的瞬間值。
__block說明符
前面我們說到,Block獲取變量時是保存自動變量的瞬間值,在Block內部如果想要改變自動變量的值,編譯器會報編譯錯誤。那么如何才能在Block內部實現修改保存自動變量的值呢,在沒用使用__block
說明符之前,我們可以根據C語言的特性來實現:
1、靜態變量
2、靜態全局變量
3、全局變量
我們來看看這段源代碼:
int global_val = 1;
static int static_global_val = 2;
int main()
{
static int static_val = 3;
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
};
return 0;
}
改源代碼里面使用Block改寫了靜態變量static_val、靜態全局變量static_global_val和全局變量global_val。轉換后的代碼如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
int global_val = 1;
static int static_global_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val; //靜態變量static_val的指針
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_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_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 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()
{
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val)); //靜態變量static_val的指針傳遞給__main_block_impl_0結構體的構造函數
return 0;
}
我們看到,對于靜態全局變量static_global_val和全局變量global_val,在轉換后的函數中直接使用。但是對于靜態變量static_val卻進行了轉換,使用靜態變量static_val的指針對其進行訪問。將靜態變量static_val的指針傳遞給__main_block_impl_0
結構體的構造函數并保存。這里我們就要問了,為什么自動變量不保存它的指針呢。
在實際使用中,自動變量分配在棧上,在由Block語法生成的Block上,經常情況下Block會在超過其變量作用域的時刻執行,當變量作用域結束時,自動變量就廢棄了,通過自動變量的指針去訪問已廢棄的自動變量,會發生錯誤。
</br>
Objective-C提供了 __block
說明符來解決這個問題。 __block
說明符用來指定Block中想變更的自動變量。且看下面的源代碼:
int main()
{
__block int val = 3; //__block修改val自動變量
void (^blk)(void) = ^{
val *= 1; //修改自動變量的值
};
return 0;
}
轉換后的代碼如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
·
·
//新增的結構體
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) *= 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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()
{
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
可以看到,僅僅是增加了一個 __block
說明符,源代碼就急劇增加。
__block
變量val變為了一個結構體實例
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
該結構體的初始化過程如下:
__Block_byref_val_0 val = {
0, //__isa
&val, //__forwarding指向自己
0, //__flags = 0
sizeof(__Block_byref_val_0), //__size 為自身__Block_byref_val_0的大小
10, //自動變量的值
}
那么如何給__block
變量賦值呢?
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) *= 1;
}
通過__main_block_impl_0
結構體中__Block_byref_val_0
結構體實例的指針找到自動變量的結構體實例,然后通過自動變量結構體的成員變量__forwarding
(__forwarding
持有指向該實例自身的指針)訪問成員變量val(原變量).
我們為什么是通過成員變量
__forwarding
而不是直接去訪問結構體中我們需要修改的變量呢? __forwarding
被設計出來的原因又是什么呢?
Block存儲域
我們知道,Block語法塊轉換為Block結構體類型(函數)的自動變量,__block
變量轉換為__block
變量的結構體類型的自動變量。而這兩種結構體類型的自動變量,存在于棧上。
通過前面的介紹,我們知道Block也是個Objective-C對象,將Block當作Objective-C對象來看待時,該Block的類為_NSConcreteStackBlock,之前我們提過Block類中有一個isa指針,在初始化時指向_NSConcreteStackBlock
,其實除了這個之外還有兩個類似的類:
- __NSConcreteStackBlock
- __NSConcreteGlobalBlock
- __NSConcreteMallocBlock
通過名字我們就可以知道__NSConcreteStackBlock
表示類的對象Block設置在棧上。
__NSConcreteGlobalBlock
表示類的對象Block設置在程序的數據區(.data)中
__NSConcreteMallocBlock
表示類的對象Block設置由malloc函數分配的內存塊(堆)中
但是到目前為止,Block使用的都是__NSConcreteStackBlock
類,都是設置在棧上。但是也不全是這樣,在記述全局變量的地方使用Block語法時,生成的Block為__NSConcreteGlobalBlock
。此外,如果Block不截獲自動變量,就可以將Block用結構體實例設置在程序的數據區(使用__NSConcreteGlobalBlock類
)。
那么什么時候使用__NSConcreteMallocBlock
這個類呢。這里我們就來順便解決一下上面遺留的問題:
- Block超出變量作用域可存在的原因
-
__block
變量用結構體成員變量__forwarding
存在的原因。
我們知道,當一個變量設置在全局區時,從變量的作用于外也可以通過指針安全的使用。但是當設置在棧上時,當作用域結束時,這個Block就被廢棄了。___block
變量也是同樣的情況。
Blocks提供了將Block和__block
變量從棧上復制到堆上的方法來解決這個問題。將配置在棧上的Block復制到堆上,這樣即使Block語法記述的變量作用域結束,堆上的Block還可以繼續存在。 當Block從棧上復制到堆上時,Block將結構體實例的isa設置成__NSConcreteMallocBlock
我們再來看看__forwarding
成員變量,我們知道,有時候__block
變量配置在堆上,也可以訪問棧上的__block
變量。在這種情況下,只要棧上的結構體實例成員變量__forwarding
指向堆上的結構體實例,那么不管是從棧上的__block
變量還是從堆上的__block
變量都能正確的訪問。
那么什么時候棧上的Block會復制到堆上呢?
- 調用Block的copy實例方法
- Block作為函數返回值返回時
- 將Block賦值為附有__strong修飾符id類型的類或Block類型成員變量時
- 在方法名中含有usingBlock的Cocoa框架方法或者Grand Central Dispatch的API中傳遞Block時。
__block變量存儲域
上一節介紹了Block的存儲,那么__block
變量又是如何處理的呢,當Block從棧復制到堆時,使用的所有__block
變量也全部被從棧復制到堆。此時,Block持有__block
變量。
在多個Block中使用__block
變量時,因為最先會將所有的Block配置在棧上,所以__block
變量也會配置在棧上。在任何一個Block從棧復制到堆時,__block
變量也會一并從棧復制到堆并被該Block所持有。當剩下的Block從棧復制到堆時,被復制的Block持有__block
變量,并增加__block
變量的引用計數。
如果配置在堆上的Block被廢棄,那么它所使用的__block
變量也就被廢棄。
之前我們提到過一句話:“不管__block
變量配置在棧上還是在堆上,都能夠訪問該變量”。這也就是__forwarding
成員變量存在的原因。
這里引用一下上面出現過的代碼:
(val->__forwarding->val) *= 1;
在使用__block
變量時,通過__forwarding
成員變量訪問val成員(原變量)。當__block
變量從棧上復制到堆上時,會將__forwarding
成員變量的值替換為復制目標堆上的__block
變量用結構體實例的地址。
ps: 棧上和堆上同時保持__block
變量實例,但是訪問和修改值則是在堆上。看張圖吧:
截獲Block對象
我們看一下源代碼:
blk_t blk;
int main()
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
} copy];
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
代碼中使用copy函數將Block從棧上復制到堆上,而使用的array這個自動變量也一并復制到堆上。(當ARC有效是,id類型以及對象類型變量必定附加所有權標識符,缺省為附有__strong
修飾符的變量)。Objective-C的運行時庫能夠準確把握Block從棧上復制到堆上以及Block被廢棄的時機,能夠在恰當的時機進行初始化和廢棄。為此需要使用在__main_block_desc_0
結構體中增加的成員變量copy和dispose,即作為指針賦值給該成員變量的__main_block_copy_0
和__main_block_dispose_0
函數。我們來依次看看這兩個函數的具體實現:
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*/);}
_Block_object_assign
函數調用,相當于retain實例方法的函數。將對象賦值在對象類型的結構體成員變量中。有retain方法,肯定有release方法。__main_block_dispose_0
函數調用__main_block_dispose_0
函數釋放賦值在Block中的結構體成員變量arrar中的對象。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
__main_block_dispose_0
函數相當于release實例方法函數,釋放賦值在對象類型的結構體成員變量中的對象。
有了這種構造,通過使用附有__strong
修飾符的自動變量,Block中截獲的對象就能夠超出其變量作用域而存在。
我們回頭看看,其實這兩個函數在使用 __block
變量時已經出現過了:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 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};
和Block結構體的部分基本相同,不同支出在于__main_block_copy_0
和__main_block_dispose_0
函數最后的參數不一樣。BLOCK_FIELD_IS_OBJECT和BLOCK_FIELD_IS_BYREF區分對象類型是對象還是__block
變量。
由此可知。Block中使用的賦值為附有__strong
修飾符的自動變量的對象和復制到堆上的__block
變量由于被堆上的Block所持有,因而可超出其變量作用域而存在。
Block循環引用
如果在Block中使用附有__strong
修飾符的自動變量,那么當Block從棧復制到堆上時,該對象為Block所持有,很容易引起循環引用。這種情況經常出現在一個類型對象中,有一個Block類型的成員變量,而這個Block中又引用了self變量,這就會造成一個循環引用。Block持有self,self持有Block。
- (id) init {
self = [super init];
blk = ^{NSLog(@"self = %@",self);
return self;
}
若由于Block引發了循環引用,根據Block的用途選擇使用__block
變量、__weak
修飾符或__unsafe_unretained
修飾符來避免循環引用。