__block的內(nèi)存管理
當(dāng)block使用外部變量時(shí)酥夭,是不能直接在block內(nèi)修改這些變量的。我們用__block修飾變量后就能夠修改了肺素。但需要說明一點(diǎn)__block只能用于auto變量無法修改,__block不能修飾全局變量、靜態(tài)變量窒典。
先看一段代碼:
- (void)blockModifyVariable {
__block int a = 10;
__block Person *person = [Person new];
person.name = @"mm";
BlockDemo block = ^{
a = 11;
person = [Person new];
person.name = @"modified";
NSLog(@"%d---%@",a,person.name);//打印結(jié)果:11---modified
};
block();
}
下面是block通過clang轉(zhuǎn)換成C++的代碼 :
struct __ViewController__blockModifyVariable_block_impl_0 {
struct __block_impl impl;
struct __ViewController__blockModifyVariable_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__Block_byref_person_1 *person; // by ref
__ViewController__blockModifyVariable_block_impl_0(void *fp, struct __ViewController__blockModifyVariable_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_person_1 *_person, int flags=0) : a(_a->__forwarding), person(_person->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
加了__block修飾后,block結(jié)構(gòu)體里面也是增加了兩個(gè)成員變量稽莉,不同的是并不是直接捕獲外部的變量瀑志,而是增加了兩個(gè)__Block_byref開頭的對(duì)象。下面看這兩個(gè)對(duì)象:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __Block_byref_person_1 {
void *__isa;
__Block_byref_person_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__strong person;
};
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ì)比兩個(gè)結(jié)構(gòu)體污秆,對(duì)象類型的person內(nèi)部多了copy和dispose這兩個(gè)管理內(nèi)存的函數(shù)劈猪。對(duì)象內(nèi)部分別有和外部變量名稱一致的a和person。并且能看出__Block_byref_person_1是強(qiáng)引用著person的良拼。__Block_byref_person_1是否強(qiáng)引用person要取決于指向外部的person變量是用什么修飾符修飾战得,如果是用weak或者_(dá)_unsafe_unretained,那這里就是弱引用。
下面是方法blockModifyVariable轉(zhuǎn)換后的代碼:
static void _I_ViewController_blockModifyVariable(ViewController * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a ={
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
10
};
__attribute__((__blocks__(byref))) __Block_byref_person_1 person = {
(void*)0,
(__Block_byref_person_1 *)&person,
33554432,
sizeof(__Block_byref_person_1),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"))
};
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)(person.__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_5067e2_mi_0);
BlockDemo block = ((void (*)())&__ViewController__blockModifyVariable_block_impl_0((void *)__ViewController__blockModifyVariable_block_func_0, &__ViewController__blockModifyVariable_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_person_1 *)&person, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
可以看出用__block修飾后將變量a和person包裝成__Block_byref_a_0和__Block_byref_person_1庸推。block初始化時(shí)將這兩個(gè)對(duì)象賦值給了block內(nèi)部的成員變量a和person常侦。
block的成員變量desc:
static struct __ViewController__blockModifyVariable_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__blockModifyVariable_block_impl_0*, struct __ViewController__blockModifyVariable_block_impl_0*);
void (*dispose)(struct __ViewController__blockModifyVariable_block_impl_0*);
} __ViewController__blockModifyVariable_block_desc_0_DATA =
{ 0,
sizeof(struct __ViewController__blockModifyVariable_block_impl_0),
__ViewController__blockModifyVariable_block_copy_0,
__ViewController__blockModifyVariable_block_dispose_0
};
static void __ViewController__blockModifyVariable_block_copy_0(struct __ViewController__blockModifyVariable_block_impl_0*dst, struct __ViewController__blockModifyVariable_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __ViewController__blockModifyVariable_block_dispose_0(struct __ViewController__blockModifyVariable_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}
desc中用copy函數(shù)和dispose函數(shù)來管理包裝后的對(duì)象a和person的內(nèi)存浇冰;
下面看看block執(zhí)行代碼塊時(shí)是如何修改和取值的:
static void __ViewController__blockModifyVariable_block_func_0(struct __ViewController__blockModifyVariable_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref 取出block內(nèi)部的成員變量a
__Block_byref_person_1 *person = __cself->person; // bound by ref 取出block內(nèi)部的成員變量person
(a->__forwarding->a) = 11; // __forwarding指針:當(dāng)block在棧上,__forwarding指向棧上block的成員變量a聋亡,當(dāng)block被拷貝到堆上__forwarding指向拷貝到堆上的block的成員變量a肘习,保證block內(nèi)部一定是訪問到堆上的變量。這一步就是通過__forwarding指針找到變量a對(duì)其修改坡倔。
// 通過__forwarding指針找到變量person對(duì)其修改漂佩。
(person->__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_5067e2_mi_1);
// 取值時(shí)也是通過__forwarding指針找到變量a、person罪塔。
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zx_b2snvmns3v14tgx8jk3ysvrm0000gn_T_ViewController_5067e2_mi_2,(a->__forwarding->a),((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("name")));
}
代碼中關(guān)于__forwarding指針指向的說明可參看下圖:
對(duì)以上做個(gè)總結(jié):用__block修飾auto變量時(shí)投蝉,編譯器會(huì)將__block變量包裝成對(duì)象,對(duì)象包含該auto變量且有個(gè)__forwarding指針指向包裝后的對(duì)象征堪,而block內(nèi)部會(huì)持有這個(gè)對(duì)象瘩缆;在block內(nèi)部訪問auto變量實(shí)際上是通過取得block內(nèi)部持有的包裝后的對(duì)象,然后通過這個(gè)對(duì)象中的__forwarding指針找到包裝后的對(duì)象或包裝后被復(fù)制到堆上的對(duì)象请契,最后取出對(duì)象中包含的變量進(jìn)行取值或修改咳榜;這就是為什么用__block修飾之后可以修改變量的原因。
__block的內(nèi)存管理
當(dāng)block在棧上時(shí)爽锥,不會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用涌韩。當(dāng)block被拷貝到堆上時(shí)會(huì)調(diào)用block內(nèi)部的copy函數(shù),copy函數(shù)調(diào)用內(nèi)部的_Block_object_assign函數(shù)根據(jù)所指向?qū)ο蟮男揎椃麑?duì)__block變量形成強(qiáng)引用(retain)或弱引用(注意:ARC會(huì)retain氯夷,MRC時(shí)不會(huì)retain)臣樱;block被拷貝到堆上后,其內(nèi)部用到的成員也都會(huì)被拷貝到堆上腮考。當(dāng)兩個(gè)棧上的block內(nèi)部訪問同一個(gè)block變量雇毫,兩個(gè)block被拷貝到堆上后,堆上只有會(huì)有一份__block變量的拷貝踩蔚,這兩個(gè)block仍同時(shí)持有這個(gè)變量棚放;參考下圖:
通過代碼也驗(yàn)證下:
- (void)modifyVariable {
__block int a = 10;
__block Person *person = [Person new];
person.name = @"mm";
BlockDemo block = ^{
a = 11;
NSLog(@"%d---%@",a,person.name);
};
BlockDemo block1 = ^{
a = 12;
NSLog(@"%d",a);
};
struct __ViewController__modifyVariable_block_impl_0* blockImpl = (__bridge struct __ViewController__modifyVariable_block_impl_0*)block;
struct __ViewController__modifyVariable_block_impl_0* blockImpl1 = (__bridge struct __ViewController__modifyVariable_block_impl_0*)block1;
NSLog(@"%p -- %p",blockImpl->a,blockImpl1->a);// 打印結(jié)果:0x600000438600 -- 0x600000438600
block();
block1();
}
// 以下是block轉(zhuǎn)換成c++后的代碼,我們通過
struct __ViewController__modifyVariable_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_a_0 {
void *__isa;
struct __Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __ViewController__modifyVariable_block_impl_0 {
struct __block_impl impl;
struct __ViewController__modifyVariable_block_desc_0* Desc;
struct __Block_byref_a_0 *a; // by ref
};
__ViewController__modifyVariable_block_impl_0是block的底層實(shí)現(xiàn),我們通過__ViewController__modifyVariable_block_impl_0來訪問block內(nèi)部持有的__block變量a馅闽,代碼中可以看到這兩個(gè)block內(nèi)部持有的a的內(nèi)存地址相同的飘蚯。
當(dāng)block從堆中移除時(shí),會(huì)調(diào)用block內(nèi)部的dispose函數(shù)福也,dispose函數(shù)內(nèi)部調(diào)用_Block_object_dispose函數(shù)局骤,_Block_object_dispose函數(shù)自動(dòng)釋放_(tái)_block變量。過程如下圖: