目錄
- Block底層解析
- 什么是block琼腔?
- block編譯轉(zhuǎn)換結(jié)構(gòu)
- block實(shí)際結(jié)構(gòu)
- block的類型
- NSConcreteGlobalBlock和NSConcreteStackBlock
- NSConcreteMallocBlock
- 捕捉變量對(duì)block結(jié)構(gòu)的影響
- 局部變量
- 全局變量
- 局部靜態(tài)變量
- __block修飾的變量
- self隱式循環(huán)引用
- 不同類型block的復(fù)制
- 棧block
- 堆block
- 全局block
- block輔助函數(shù)
- __block修飾的基本類型的輔助函數(shù)
- 對(duì)象的輔助函數(shù)
- ARC中block的工作
- block試驗(yàn)
- block作為參數(shù)傳遞
- block作為返回值
- block屬性
- 什么是block琼腔?
Block底層解析(封裝了函數(shù)調(diào)用和函數(shù)調(diào)用的oc對(duì)象)
最近看了一些block的資料,并動(dòng)手做了一些實(shí)踐,摘錄并添加了一些結(jié)論。
什么是block矢否?
首先饮焦,看一個(gè)極簡(jiǎn)的block:
int main(int argc, const char * argv[]) {
@autoreleasepool {
^{ };
}
return 0;
}
block編譯轉(zhuǎn)換結(jié)構(gòu)
對(duì)其執(zhí)行clang -rewrite-objc
編譯轉(zhuǎn)換成C++實(shí)現(xiàn),得到以下代碼:
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) {
}
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;
(void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
}
return 0;
}
不難看出其中的__main_block_impl_0
就是block的一個(gè)C++的實(shí)現(xiàn)(最后面的_0
代表是main中的第幾個(gè)block)勒虾,也就是說(shuō)也是一個(gè)結(jié)構(gòu)體
。
其中__block_impl
的定義如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
其結(jié)構(gòu)體成員如下:
- isa瘸彤,指向所屬類的指針修然,也就是block的類型
- flags,標(biāo)志變量质况,在實(shí)現(xiàn)block的內(nèi)部操作時(shí)會(huì)用到
- Reserved愕宋,保留變量
- FuncPtr,block執(zhí)行時(shí)調(diào)用的函數(shù)指針
可以看出结榄,它包含了isa指針(包含isa指針的皆為對(duì)象)中贝,也就是說(shuō)block也是一個(gè)對(duì)象
(runtime里面,對(duì)象和類都是用結(jié)構(gòu)體表示)臼朗。
__main_block_desc_0
的定義如下:
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)};
其結(jié)構(gòu)成員含義如下:
- reserved:保留字段
- Block_size:block大小(sizeof(struct __main_block_impl_0))
以上代碼在定義__main_block_desc_0
結(jié)構(gòu)體時(shí)邻寿,同時(shí)創(chuàng)建了__main_block_desc_0_DATA
,并給它賦值视哑,以供在main
函數(shù)中對(duì)__main_block_impl_0
進(jìn)行初始化绣否。
__main_block_impl_0
定義了顯式的構(gòu)造函數(shù),其函數(shù)體如下:
__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;
}
可以看出挡毅,
-
__main_block_impl_0
的isa
指針指向了_NSConcreteStackBlock
蒜撮, - 從
main
函數(shù)中看,__main_block_impl_0
的FuncPtr
指向了函數(shù)__main_block_func_0
-
__main_block_impl_0
的Desc
也指向了定義__main_block_desc_0
時(shí)就創(chuàng)建的__main_block_desc_0_DATA
慷嗜,其中紀(jì)錄了block結(jié)構(gòu)體大小等信息淀弹。
以上就是根據(jù)編譯轉(zhuǎn)換的結(jié)果丹壕,對(duì)一個(gè)簡(jiǎn)單block的解析,后面會(huì)將block操作不同類型的外部變量
薇溃,對(duì)block結(jié)構(gòu)的影響進(jìn)行相應(yīng)的說(shuō)明菌赖。
block實(shí)際結(jié)構(gòu)
接下來(lái)觀察下Block_private.h
文件中對(duì)block的相關(guān)結(jié)構(gòu)體的真實(shí)定義:
/* Revised new layout. */
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
有了上文對(duì)編譯轉(zhuǎn)換的分析,這里只針對(duì)略微不同的成員進(jìn)行分析:
invoke沐序,同上文的FuncPtr琉用,block執(zhí)行時(shí)調(diào)用的函數(shù)指針,block定義時(shí)內(nèi)部的執(zhí)行代碼都在這個(gè)函數(shù)中
-
Block_descriptor策幼,block的詳細(xì)描述
- copy/dispose邑时,輔助拷貝/銷毀函數(shù),處理block范圍外的變量時(shí)使用
總體來(lái)說(shuō)特姐,block就是一個(gè)里面存儲(chǔ)了指向函數(shù)體中包含定義block時(shí)的代碼塊
的函數(shù)指針晶丘,以及block外部上下文
變量等信息的結(jié)構(gòu)體。
block的類型
block的常見(jiàn)類型有3種:
- _NSConcreteGlobalBlock(全局)
- _NSConcreteStackBlock(棧)
- _NSConcreteMallocBlock(堆)
附上APUE的進(jìn)程虛擬內(nèi)存段分布圖:
其中前2種在Block.h
種聲明唐含,后1種在Block_private.h
中聲明浅浮,所以最后1種基本不會(huì)在源碼中出現(xiàn)。
由于無(wú)法直接創(chuàng)建_NSConcreteMallocBlock
類型的block捷枯,所以這里只對(duì)前面2種進(jìn)行手動(dòng)創(chuàng)建分析滚秩,最后1種通過(guò)源代碼分析。
NSConcreteGlobalBlock和NSConcreteStackBlock
首先淮捆,根據(jù)前面兩種類型郁油,編寫以下代碼:
void (^globalBlock)() = ^{
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^stackBlock1)() = ^{
};
}
return 0;
}
對(duì)其進(jìn)行編譯轉(zhuǎn)換后得到以下縮略代碼:
// globalBlock
struct __globalBlock_block_impl_0 {
struct __block_impl impl;
struct __globalBlock_block_desc_0* Desc;
__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
// stackBlock
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;
}
};
...
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
}
return 0;
}
可以看出globalBlock
的isa指向了_NSConcreteGlobalBlock
,即在全局區(qū)域創(chuàng)建攀痊,編譯時(shí)具體的代碼就已經(jīng)確定在上圖中的代碼段中了桐腌,block變量存儲(chǔ)在全局?jǐn)?shù)據(jù)存儲(chǔ)區(qū);stackBlock
的isa指向了_NSConcreteStackBlock
蚕苇,即在棧區(qū)創(chuàng)建哩掺。
NSConcreteMallocBlock
接下來(lái)是在堆中的block,堆中的block無(wú)法直接創(chuàng)建涩笤,其需要由_NSConcreteStackBlock
類型的block拷貝而來(lái)(也就是說(shuō)block需要執(zhí)行copy之后才能存放到堆中
)。由于block的拷貝最終都會(huì)調(diào)用_Block_copy_internal
函數(shù)盒件,所以觀察這個(gè)函數(shù)就可以知道堆中block是如何被創(chuàng)建的了:
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
...
aBlock = (struct Block_layout *)arg;
...
// Its a stack block. Make a copy.
if (!isGC) {
// 申請(qǐng)block的堆內(nèi)存
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 拷貝棧中block到剛申請(qǐng)的堆內(nèi)存中
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 改變isa指向_NSConcreteMallocBlock蹬碧,即堆block類型
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
else {
...
}
}
從以上代碼以及注釋可以很清楚的看出,函數(shù)通過(guò)memmove
將棧中的block的內(nèi)容拷貝到了堆中炒刁,并使isa指向了_NSConcreteMallocBlock
恩沽。
block主要的一些學(xué)問(wèn)就出在棧中block向堆中block的轉(zhuǎn)移過(guò)程中了。
捕捉變量對(duì)block結(jié)構(gòu)的影響
接下來(lái)會(huì)編譯轉(zhuǎn)換捕捉不同變量類型的block翔始,以對(duì)比它們的區(qū)別罗心。
局部變量
前:
- (void)test
{
int a;
^{a;};
}
后:
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
int a;
// a(_a)是構(gòu)造函數(shù)的參數(shù)列表初始化形式里伯,相當(dāng)于a = _a。從_I_Person_test看渤闷,傳入的就是a
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
a;}
static struct __Person__test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};
static void _I_Person_test(Person * self, SEL _cmd) {
int a;
(void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, a);
}
可以看到疾瓮,block相對(duì)于文章開(kāi)頭增加了一個(gè)int類型的成員變量,他就是用來(lái)存儲(chǔ)外部變量a的飒箭±堑纾可以看出,這次拷貝只是一次值傳遞
弦蹂。并且當(dāng)我們想在block中進(jìn)行以下操作時(shí)肩碟,將會(huì)發(fā)生錯(cuò)誤
^{a = 10;};
編譯器會(huì)提示
。因?yàn)開(kāi)I_Person_test函數(shù)中的a和__Person__test_block_func_0函數(shù)中的a并沒(méi)有在同一個(gè)作用域凸椿,所以在block對(duì)a進(jìn)行賦值是沒(méi)有意義的削祈,所以編譯器給出了錯(cuò)誤。我們可以通過(guò)地址傳遞來(lái)消除以上錯(cuò)誤:
- (void)test
{
int a = 0;
// 利用指針p存儲(chǔ)a的地址
int *p = &a;
^{
// 通過(guò)a的地址設(shè)置a的值
*p = 10;
};
}
但是變量a的生命周期是和方法test的棧相關(guān)聯(lián)的脑漫,當(dāng)test運(yùn)行結(jié)束岩瘦,棧隨之銷毀,那么變量a就會(huì)被銷毀窿撬,p也就成為了野指針启昧。如果block是作為參數(shù)或者返回值,這些類型都是跨棧的劈伴,也就是說(shuō)再次調(diào)用會(huì)造成野指針錯(cuò)誤密末。
全局變量
前:
// 全局靜態(tài)
static int a;
// 全局
int b;
- (void)test
{
^{
a = 10;
b = 10;
};
}
后:
static int a;
int b;
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
a = 10;
b = 10;
}
static struct __Person__test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};
static void _I_Person_test(Person * self, SEL _cmd) {
(void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA);
}
可以看出,因?yàn)槿肿兞慷际窃?code>靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū)跛璧,在程序結(jié)束前不會(huì)被銷毀严里,所以block直接訪問(wèn)了對(duì)應(yīng)的變量,而沒(méi)有在__Person__test_block_impl_0結(jié)構(gòu)體中給變量預(yù)留位置追城。
局部靜態(tài)變量
前
- (void)test
{
static int a;
^{
a = 10;
};
}
后:
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
int *a;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
int *a = __cself->a; // bound by copy
// 這里通過(guò)局部靜態(tài)變量a的地址來(lái)對(duì)其進(jìn)行修改
(*a) = 10;
}
static struct __Person__test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0)};
static void _I_Person_test(Person * self, SEL _cmd) {
static int a;
// 傳入a的地址
(void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, &a);
}
需要注意一點(diǎn)的是靜態(tài)局部變量是存儲(chǔ)在靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū)域的刹碾,也就是和程序擁有一樣的生命周期
,也就是說(shuō)在程序運(yùn)行時(shí)座柱,都能夠保證block訪問(wèn)到一個(gè)有效的變量迷帜。但是其作用范圍
還是局限于定義它的函數(shù)中,所以只能在block通過(guò)靜態(tài)局部變量的地址
來(lái)進(jìn)行訪問(wèn)色洞。
關(guān)于變量的存儲(chǔ)我原來(lái)的這篇博客有提及:c語(yǔ)言臆想--全局---局部變量
__block修飾的變量
前:
- (void)test
{
__block int a;
^{
a = 10;
};
}
后:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
// 注意戏锹,這里的_forwarding用來(lái)保證操作的始終是堆中的拷貝a,而不是棧中的a
(a->__forwarding->a) = 10;
}
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __Person__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
static void _I_Person_test(Person * self, SEL _cmd) {
// __block將a包裝成了一個(gè)對(duì)象
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0)};
;
(void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
}
可以看到火诸,對(duì)比上面的結(jié)果锦针,明顯多了__Block_byref_a_0
結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體中含有isa
指針,所以也是一個(gè)對(duì)象奈搜,它是用來(lái)包裝局部變量a的悉盆。當(dāng)block被copy到堆中時(shí),__Person__test_block_impl_0
的拷貝輔助函數(shù)__Person__test_block_copy_0
會(huì)將__Block_byref_a_0
拷貝至堆中馋吗,所以即使局部變量所在堆被銷毀焕盟,block依然能對(duì)堆中的局部變量進(jìn)行操作。其中__Block_byref_a_0
成員指針__forwarding
用來(lái)指向它在堆中的拷貝耗美,其依據(jù)源碼如下:
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
...
// 堆中拷貝的forwarding指向它自己
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
// 棧中的forwarding指向堆中的拷貝
src->forwarding = copy; // patch stack to point to heap copy
...
}
這樣做是為了保證操作的值始終是堆中的拷貝京髓,而不是棧中的值。(處理在局部變量所在棧還沒(méi)銷毀商架,就調(diào)用block來(lái)改變局部變量值的情況堰怨,如果沒(méi)有__forwarding指針,則修改無(wú)效)
至于block如何實(shí)現(xiàn)對(duì)局部變量的拷貝蛇摸,下面會(huì)詳細(xì)說(shuō)明备图。
self隱式循環(huán)引用
前:
@implementation Person
{
int _a;
void (^_block)();
}
- (void)test
{
void (^_block)() = ^{
_a = 10;
};
}
@end
后:
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
// 可以看到,block強(qiáng)引用了self
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy
(*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;
}
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __Person__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};
static void _I_Person_test(Person * self, SEL _cmd) {
void (*_block)() = (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344);
}
如果在編譯轉(zhuǎn)換前赶袄,將_a
改成self.a
揽涮,能很明顯地看出是產(chǎn)生了循環(huán)引用(self強(qiáng)引用block,block強(qiáng)引用self)饿肺。那么使用_a
呢蒋困?經(jīng)過(guò)編譯轉(zhuǎn)換后,依然可以在__Person__test_block_impl_0
看見(jiàn)self
的身影敬辣。且在函數(shù)_I_Person_test
中雪标,傳入的參數(shù)也是self
甘凭。通過(guò)以下語(yǔ)句裕偿,可以看出,不管是用什么形式訪問(wèn)實(shí)例變量栈幸,最終都會(huì)轉(zhuǎn)換成self+變量?jī)?nèi)存偏移的形式
撰茎。所以在上面例子中使用_a
也會(huì)造成循環(huán)引用嵌牺。
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
Person *self = __cself->self; // bound by copy
// self+實(shí)例變量a的偏移值
(*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;
}
不同類型block的復(fù)制
block
的復(fù)制代碼在_Block_copy_internal
函數(shù)中。
棧block
從以下代碼可以看出龄糊,棧block的復(fù)制不僅僅復(fù)制了其內(nèi)容逆粹,還添加了一些額外的東西
- 1、往flags中并入了
BLOCK_NEEDS_FREE
(這個(gè)標(biāo)志表明block需要釋放绎签,在release
以及再次拷貝
時(shí)會(huì)用到) - 2枯饿、如果有輔助copy函數(shù)(
BLOCK_HAS_COPY_DISPOSE
),那么就調(diào)用(這個(gè)輔助copy函數(shù)是用來(lái)拷貝block捕獲的變量
的)
...
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
...
堆block
從以下代碼看出诡必,如果block的flags中有BLOCK_NEEDS_FREE
標(biāo)志(block從棧中拷貝到堆時(shí)添加的標(biāo)志),就執(zhí)行latching_incr_int
操作,其功能就是讓block的引用計(jì)數(shù)加1爸舒。所以堆中block的拷貝只是單純地改變了引用計(jì)數(shù)
...
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
...
全局block
從以下代碼看出蟋字,對(duì)于全局block,函數(shù)沒(méi)有做任何操作扭勉,直接返回了傳入的block
...
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
...
block輔助函數(shù)
上文提及到了block輔助copy與dispose處理函數(shù)鹊奖,這里分析下這兩個(gè)函數(shù)的內(nèi)部實(shí)現(xiàn)。在捕獲變量為__block
修飾的基本類型
涂炎,或者為對(duì)象
時(shí)忠聚,block才會(huì)有這兩個(gè)輔助函數(shù)。
block捕捉變量
拷貝函數(shù)為_Block_object_assign
唱捣。在調(diào)用復(fù)制block的函數(shù)_Block_copy_internal時(shí)两蟀,會(huì)根據(jù)block有無(wú)輔助函數(shù)來(lái)對(duì)捕捉變量
拷貝函數(shù)_Block_object_assign
進(jìn)行調(diào)用。而在_Block_object_assign
函數(shù)中震缭,也會(huì)判斷捕捉變量
包裝而成的對(duì)象(Block_byref結(jié)構(gòu)體)是否有輔助函數(shù)赂毯,來(lái)進(jìn)行調(diào)用。
__block
修飾的基本類型的輔助函數(shù)
編寫以下代碼:
typedef void(^Block)();
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a;
Block block = ^ {
a;
};
}
轉(zhuǎn)換成C++代碼后:
typedef void(*Block)();
// __block int a
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
// block
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block函數(shù)體
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a);
}
// 輔助copy函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
// 輔助dispose函數(shù)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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;
// 這里創(chuàng)建了拣宰,并將a的flags設(shè)置為0
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0)};
;
Block block = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
}
return 0;
}
從上面代碼中党涕,被__block
修飾的a變量變?yōu)榱?code>__Block_byref_a_0類型,根據(jù)這個(gè)格式巡社,從源碼中查看得到相似的定義:
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
int flags; /* refcount; */
int size;
void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
/* long shared[0]; */
};
// 做下對(duì)比
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
// flags/_flags類型
enum {
/* See function implementation for a more complete description of these fields and combinations */
// 是一個(gè)對(duì)象
BLOCK_FIELD_IS_OBJECT = 3, /* id, NSObject, __attribute__((NSObject)), block, ... */
// 是一個(gè)block
BLOCK_FIELD_IS_BLOCK = 7, /* a block variable */
// 被__block修飾的變量
BLOCK_FIELD_IS_BYREF = 8, /* the on stack structure holding the __block variable */
// 被__weak修飾的變量膛堤,只能被輔助copy函數(shù)使用
BLOCK_FIELD_IS_WEAK = 16, /* declared __weak, only used in byref copy helpers */
// block輔助函數(shù)調(diào)用(告訴內(nèi)部實(shí)現(xiàn)不要進(jìn)行retain或者copy)
BLOCK_BYREF_CALLER = 128 /* called from __block (byref) copy/dispose support routines. */
};
可以看出,__block
將原來(lái)的基本類型包裝成了對(duì)象
晌该。因?yàn)橐陨蟽蓚€(gè)結(jié)構(gòu)體的前4個(gè)成員的類型都是一樣的肥荔,內(nèi)存空間排列一致,所以可以進(jìn)行以下操作:
// 轉(zhuǎn)換成C++代碼
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
// _Block_object_assign源碼
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
...
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
// copying a __block reference from the stack Block to the heap
// flags will indicate if it holds a __weak reference and needs a special isa
_Block_byref_assign_copy(destAddr, object, flags);
}
...
}
// _Block_byref_assign_copy源碼
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
// 這里因?yàn)榍懊?個(gè)成員的內(nèi)存分布一樣气笙,所以直接轉(zhuǎn)換后次企,使用Block_byref的成員變量名,能訪問(wèn)到__Block_byref_a_0的前面4個(gè)成員
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
...
else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// 從main函數(shù)對(duì)__Block_byref_a_0的初始化潜圃,可以看到初始化時(shí)將flags賦值為0
// 這里表示第一次拷貝缸棵,會(huì)進(jìn)行復(fù)制操作,并修改原來(lái)flags的值
// static int _Byref_flag_initial_value = BLOCK_NEEDS_FREE | 2;
// 可以看出谭期,復(fù)制后堵第,會(huì)并入BLOCK_NEEDS_FREE,后面的2是block的初始引用計(jì)數(shù)
...
copy->flags = src->flags | _Byref_flag_initial_value;
...
}
// 已經(jīng)拷貝到堆了隧出,只增加引用計(jì)數(shù)
else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
// 普通的賦值踏志,里面最底層就*destptr = value;這句表達(dá)式
_Block_assign(src->forwarding, (void **)destp);
}
主要操作都在代碼注釋中了,總體來(lái)說(shuō)胀瞪,__block
修飾的基本類型會(huì)被包裝為對(duì)象针余,并且只在最初block拷貝時(shí)復(fù)制一次饲鄙,后面的拷貝只會(huì)增加這個(gè)捕獲變量的引用計(jì)數(shù)。
對(duì)象的輔助函數(shù)
- 沒(méi)有
__block
修飾
typedef void(^Block)();
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *a = [[NSObject alloc] init];
Block block = ^ {
a;
};
}
return 0;
}
首先圆雁,在沒(méi)有__block
修飾時(shí)忍级,對(duì)象編譯轉(zhuǎn)換的結(jié)果如下,刪除了一些變化不大的代碼:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSObject *a = __cself->a; // bound by copy
a;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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),
對(duì)象在沒(méi)有__block
修飾時(shí)伪朽,并沒(méi)有產(chǎn)生__Block_byref_a_0
結(jié)構(gòu)體轴咱,只是將標(biāo)志位修改為BLOCK_FIELD_IS_OBJECT
。而在_Block_object_assign
中對(duì)應(yīng)的判斷分支代碼如下:
...
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
_Block_retain_object(object);
_Block_assign((void *)object, destAddr);
}
...
可以看到烈涮,block復(fù)制時(shí)朴肺,會(huì)retain捕捉對(duì)象,以增加其引用計(jì)數(shù)坚洽。
- 有
__block
修飾
typedef void(^Block)();
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSObject *a = [[NSObject alloc] init];
Block block = ^ {
a;
};
}
return 0;
}
在這種情況下戈稿,編譯轉(zhuǎn)換的部分結(jié)果如下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *a;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131,....};
Block block = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
}
// 以下的40表示__Block_byref_a_0對(duì)象a的位移(4個(gè)指針(32字節(jié))+2個(gè)int變量(8字節(jié))=40字節(jié))
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);
}
可以看到,對(duì)于對(duì)象酪术,__Block_byref_a_0
另外增加了兩個(gè)輔助函數(shù)__Block_byref_id_object_copy
器瘪、__Block_byref_id_object_dispose
,以實(shí)現(xiàn)對(duì)對(duì)象內(nèi)存的管理。其中兩者的最后一個(gè)參數(shù)131
表示BLOCK_BYREF_CALLER
|BLOCK_FIELD_IS_OBJECT
绘雁,BLOCK_BYREF_CALLER表示在內(nèi)部實(shí)現(xiàn)中不對(duì)a對(duì)象進(jìn)行retain或copy橡疼;以下為相關(guān)源碼:
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
...
else {
// do *not* retain or *copy* __block variables whatever they are
_Block_assign((void *)object, destAddr);
}
}
_Block_byref_assign_copy
函數(shù)的以下代碼會(huì)對(duì)上面的輔助函數(shù)(__Block_byref_id_object_copy_131)進(jìn)行調(diào)用;570425344
表示BLOCK_HAS_COPY_DISPOSE
|BLOCK_HAS_DESCRIPTOR
庐舟,所以會(huì)執(zhí)行以下相關(guān)源碼:
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
ARC中block的工作
蘋果文檔提及欣除,在ARC模式下,在棧間傳遞block時(shí)挪略,不需要手動(dòng)copy棧中的block历帚,即可讓block正常工作。主要原因是ARC對(duì)棧中的block自動(dòng)執(zhí)行了copy杠娱,將_NSConcreteStackBlock
類型的block轉(zhuǎn)換成了_NSConcreteMallocBlock
的block挽牢。
block試驗(yàn)
下面對(duì)block做點(diǎn)實(shí)驗(yàn):
int main(int argc, const char * argv[]) {
@autoreleasepool {
int i = 10;
void (^block)() = ^{i;};
__weak void (^weakBlock)() = ^{i;};
void (^stackBlock)() = ^{};
// ARC情況下
// 創(chuàng)建時(shí),都會(huì)在棧中
// <__NSStackBlock__: 0x7fff5fbff730>
NSLog(@"%@", ^{i;});
// 因?yàn)閎lock為strong類型摊求,且捕獲了外部變量禽拔,所以賦值時(shí),自動(dòng)進(jìn)行了copy
// <__NSMallocBlock__: 0x100206920>
NSLog(@"%@", block);
// 如果是weak類型的block室叉,依然不會(huì)自動(dòng)進(jìn)行copy
// <__NSStackBlock__: 0x7fff5fbff728>
NSLog(@"%@", weakBlock);
// 如果block是strong類型睹栖,并且沒(méi)有捕獲外部變量,那么就會(huì)轉(zhuǎn)換成__NSGlobalBlock__
// <__NSGlobalBlock__: 0x100001110>
NSLog(@"%@", stackBlock);
// 在非ARC情況下茧痕,產(chǎn)生以下輸出
// <__NSStackBlock__: 0x7fff5fbff6d0>
// <__NSStackBlock__: 0x7fff5fbff730>
// <__NSStackBlock__: 0x7fff5fbff700>
// <__NSGlobalBlock__: 0x1000010d0>
}
return 0;
}
可以看出野来,ARC對(duì)類型為strong
且捕獲了外部變量
的block進(jìn)行了copy。并且當(dāng)block類型為strong
踪旷,但是創(chuàng)建時(shí)沒(méi)有捕獲外部變量
曼氛,block最終會(huì)變成__NSGlobalBlock__
類型(這里可能因?yàn)閎lock中的代碼沒(méi)有捕獲外部變量豁辉,所以不需要在棧中開(kāi)辟變量,也就是說(shuō)搪锣,在編譯
時(shí)秋忙,這個(gè)block的所有內(nèi)容已經(jīng)在代碼段中生成了
彩掐,所以就把block的類型轉(zhuǎn)換為全局類型)
block作為參數(shù)傳遞
再來(lái)看下使用在棧中的block需要注意的情況:
NSMutableArray *arrayM;
void myBlock()
{
int a = 5;
Block block = ^ {
NSLog(@"%d", a);
};
[arrayM addObject:block];
NSLog(@"%@", block);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
arrayM = @[].mutableCopy;
myBlock();
Block block = [arrayM firstObject];
// 非ARC這里崩潰
block();
}
// ARC情況下輸出
// <__NSMallocBlock__: 0x100214480>
// 非ARC情況下輸出
// <__NSStackBlock__: 0x7fff5fbff738>
// 崩潰构舟,野指針錯(cuò)誤
可以看到,ARC情況下因?yàn)樽詣?dòng)執(zhí)行了copy堵幽,所以返回類型為__NSMallocBlock__
狗超,在函數(shù)結(jié)束后依然可以訪問(wèn);而非ARC情況下朴下,需要我們手動(dòng)調(diào)用[block copy]
來(lái)將block拷貝到堆中努咐,否則因?yàn)闂V械腷lock生命周期和函數(shù)中的棧生命周期關(guān)聯(lián),當(dāng)函數(shù)退出后殴胧,相應(yīng)的堆被銷毀渗稍,block也就不存在了。
如果把block的以下代碼刪除:
NSLog(@"%d", a);
那么block就會(huì)變成全局類型团滥,在main中訪問(wèn)也不會(huì)出崩潰竿屹。
block作為返回值
在非ARC情況下,如果返回值是block灸姊,則一般這樣操作:
return [[block copy] autorelease];
對(duì)于外部要使用的block拱燃,更趨向于把它拷貝到堆中,使其脫離棧生命周期的約束力惯。
block屬性
這里還有一點(diǎn)關(guān)于block類型的ARC屬性碗誉。上文也說(shuō)明了,ARC會(huì)自動(dòng)幫strong類型
且捕獲外部變量
的block進(jìn)行copy父晶,所以在定義block類型的屬性時(shí)也可以使用strong哮缺,不一定使用copy。也就是以下代碼:
/** 假如有棧block賦給以下兩個(gè)屬性 **/
// 這里因?yàn)锳RC甲喝,當(dāng)棧block中會(huì)捕獲外部變量時(shí)尝苇,這個(gè)block會(huì)被copy進(jìn)堆中
// 如果沒(méi)有捕獲外部變量,這個(gè)block會(huì)變?yōu)槿诸愋?// 不管怎么樣俺猿,它都脫離了棧生命周期的約束
@property (strong, nonatomic) Block *strongBlock;
// 這里都會(huì)被copy進(jìn)堆中
@property (copy, nonatomic) Block *copyBlock;
參考文章
深入研究Block捕獲外部變量和__block實(shí)現(xiàn)原理
一篇文章看懂iOS代碼塊Block