一句狼、block對對象變量的捕獲
block一般使用過程中都是對對象變量的捕獲,那么對象變量的捕獲同基本數(shù)據(jù)類型變量相同嗎热某?
查看一下代碼思考:當(dāng)在block中訪問的為對象類型時腻菇,對象什么時候會銷毀?
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"------block內(nèi)部%d",person.age);
};
} // 執(zhí)行完畢昔馋,person沒有被釋放
NSLog(@"--------");
} // person 釋放
return 0;
}
大括號執(zhí)行完畢之后筹吐,person依然不會被釋放。上一篇文章提到過秘遏,person為aotu變量丘薛,傳入的block的變量同樣為person,即block有一個強(qiáng)引用引用person邦危,所以block不被銷毀的話洋侨,peroson也不會銷毀。
__weak
__weak添加之后倦蚪,person在作用域執(zhí)行完畢之后就被銷毀了希坚。
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *waekPerson = person;
block = ^{
NSLog(@"------block內(nèi)部%d",waekPerson.age);
};
}
NSLog(@"--------");
}
return 0;
}
__weak修飾的變量,在生成的__main_block_impl_0中也是使用__weak修飾审丘。
__main_block_copy_0 和 __main_block_dispose_0
當(dāng)block中捕獲對象類型的變量時吏够,我們發(fā)現(xiàn)block結(jié)構(gòu)體__main_block_impl_0的描述結(jié)構(gòu)體__main_block_desc_0中多了兩個參數(shù)copy和dispose函數(shù)。
copy和dispose函數(shù)中傳入的都是__main_block_impl_0結(jié)構(gòu)體本身。
copy本質(zhì)就是__main_block_copy_0函數(shù)锅知,__main_block_copy_0函數(shù)內(nèi)部調(diào)用_Block_object_assign函數(shù)播急,_Block_object_assign中傳入的是person對象的地址,person對象售睹,以及8桩警。
dispose本質(zhì)就是__main_block_dispose_0函數(shù),__main_block_dispose_0函數(shù)內(nèi)部調(diào)用_Block_object_dispose函數(shù)昌妹,_Block_object_dispose函數(shù)傳入的參數(shù)是person對象捶枢,以及8。
總結(jié)
1.一旦block中捕獲的變量為對象類型飞崖,block結(jié)構(gòu)體中的__main_block_desc_0會出兩個參數(shù)copy和dispose烂叔。因?yàn)樵L問的是個對象,block希望擁有這個對象固歪,就需要對對象進(jìn)行引用蒜鸡,也就是進(jìn)行內(nèi)存管理的操作。比如說對對象進(jìn)行retarn操作牢裳,因此一旦block捕獲的變量是對象類型就會會自動生成copy和dispose來對內(nèi)部引用的對象進(jìn)行內(nèi)存管理逢防。
2.當(dāng)block內(nèi)部訪問了對象類型的auto變量時,如果block是在棧上蒲讯,block內(nèi)部不會對person產(chǎn)生強(qiáng)引用忘朝。不論block結(jié)構(gòu)體內(nèi)部的變量是__strong修飾還是__weak修飾,都不會對變量產(chǎn)生強(qiáng)引用判帮。
3.如果block被拷貝到堆上局嘁。copy函數(shù)會調(diào)用_Block_object_assign函數(shù),根據(jù)auto變量的修飾符(__strong脊另,__weak导狡,unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用或者弱引用偎痛。
4.如果block從堆中移除旱捧,dispose函數(shù)會調(diào)用_Block_object_dispose函數(shù),自動釋放引用的auto變量踩麦。
二枚赡、block內(nèi)修改變量的值
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
Block block = ^ {
// age = 20; // 無法修改
NSLog(@"%d",age);
};
block();
}
return 0;
}
默認(rèn)情況下block不能修改外部的局部變量。通過之前對源碼的分析可以知道谓谦。
age是在main函數(shù)內(nèi)部聲明的贫橙,說明age的內(nèi)存存在于main函數(shù)的棧空間內(nèi)部反粥,但是block內(nèi)部的代碼在__main_block_func_0函數(shù)內(nèi)部卢肃。__main_block_func_0函數(shù)內(nèi)部無法訪問age變量的內(nèi)存空間疲迂,兩個函數(shù)的棧空間不一樣莫湘,__main_block_func_0內(nèi)部拿到的age是block結(jié)構(gòu)體內(nèi)部的age尤蒿,因此無法在__main_block_func_0函數(shù)內(nèi)部去修改main函數(shù)內(nèi)部的變量。
方式一:age使用static修飾幅垮。
前文提到過static修飾的age變量傳遞到block內(nèi)部的是指針腰池,在__main_block_func_0函數(shù)內(nèi)部就可以拿到age變量的內(nèi)存地址,因此就可以在block內(nèi)部修改age的值忙芒。
方式二:__block
__block用于解決block內(nèi)部不能修改auto變量值的問題示弓,__block不能修飾靜態(tài)變量(static) 和全局變量。
首先被__block修飾的age變量聲明變?yōu)槊麨閍ge的__Block_byref_age_0結(jié)構(gòu)體呵萨,也就是說加上__block修飾的話捕獲到的block內(nèi)的變量為__Block_byref_age_0類型的結(jié)構(gòu)體奏属。
接著將__Block_byref_age_0結(jié)構(gòu)體age存入__main_block_impl_0結(jié)構(gòu)體中,并賦值給__Block_byref_age_0 *age;
之后調(diào)用block甘桑,首先取出__main_block_impl_0中的age拍皮,通過age結(jié)構(gòu)體拿到__forwarding指針,上面提到過__forwarding中保存的就是__Block_byref_age_0結(jié)構(gòu)體本身跑杭,這里也就是age(__Block_byref_age_0),在通過__forwarding拿到結(jié)構(gòu)體中的age(10)變量并修改其值咆耿。
為什么要通過__forwarding獲取age變量的值德谅?
__forwarding是指向自己的指針。這樣的做法是為了方便內(nèi)存管理萨螺,之后內(nèi)存管理章節(jié)會詳細(xì)解釋窄做。
到此為止,__block為什么能修改變量的值已經(jīng)很清晰了慰技。__block將變量包裝成對象椭盏,然后在把a(bǔ)ge封裝在結(jié)構(gòu)體里面,block內(nèi)部存儲的變量為結(jié)構(gòu)體指針吻商,也就可以通過指針找到內(nèi)存地址進(jìn)而修改變量的值掏颊。
__block修飾對象類型
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
NSLog(@"%@",person);
Block block = ^{
person = [[Person alloc] init];
NSLog(@"%@",person);
};
block();
}
return 0;
}
通過源碼查看,將對象包裝在一個新的結(jié)構(gòu)體中艾帐。結(jié)構(gòu)體內(nèi)部會有一個person對象乌叶,不一樣的地方是結(jié)構(gòu)體內(nèi)部添加了內(nèi)存管理的兩個函數(shù)__Block_byref_id_object_copy和__Block_byref_id_object_dispose
__block內(nèi)存管理
上文提到當(dāng)block中捕獲對象類型的變量時,block中的__main_block_desc_0結(jié)構(gòu)體內(nèi)部會自動添加copy和dispose函數(shù)對捕獲的變量進(jìn)行內(nèi)存管理柒爸。
那么同樣的當(dāng)block內(nèi)部捕獲__block修飾的對象類型的變量時准浴,__Block_byref_person_0結(jié)構(gòu)體內(nèi)部也會自動添加__Block_byref_id_object_copy和__Block_byref_id_object_dispose對被__block包裝成結(jié)構(gòu)體的對象進(jìn)行內(nèi)存管理。
當(dāng)block內(nèi)存在棧上時捎稚,并不會對__block變量產(chǎn)生內(nèi)存管理乐横。當(dāng)blcok被copy到堆上時
會調(diào)用block內(nèi)部的copy函數(shù)求橄,copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù),_Block_object_assign函數(shù)會對__block變量形成強(qiáng)引用(相當(dāng)于retain)葡公。
__block修飾的變量在block結(jié)構(gòu)體中一直都是強(qiáng)引用谈撒,而其他類型的是由傳入的對象指針類型決定。
一段代碼更深入的觀察一下匾南。
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int number = 20;
__block int age = 10;
NSObject *object = [[NSObject alloc] init];
__weak NSObject *weakObj = object;
Person *p = [[Person alloc] init];
__block Person *person = p;
__block __weak Person *weakPerson = p;
Block block = ^ {
NSLog(@"%d",number); // 局部變量
NSLog(@"%d",age); // __block修飾的局部變量
NSLog(@"%p",object); // 對象類型的局部變量
NSLog(@"%p",weakObj); // __weak修飾的對象類型的局部變量
NSLog(@"%p",person); // __block修飾的對象類型的局部變量
NSLog(@"%p",weakPerson); // __block啃匿,__weak修飾的對象類型的局部變量
};
block();
}
return 0;
}
將上述代碼轉(zhuǎn)化為c++代碼查看不同變量之間的區(qū)別
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int number;
NSObject *__strong object;
NSObject *__weak weakObj;
__Block_byref_age_0 *age; // by ref
__Block_byref_person_1 *person; // by ref
__Block_byref_weakPerson_2 *weakPerson; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, NSObject *__strong _object, NSObject *__weak _weakObj, __Block_byref_age_0 *_age, __Block_byref_person_1 *_person, __Block_byref_weakPerson_2 *_weakPerson, int flags=0) : number(_number), object(_object), weakObj(_weakObj), age(_age->__forwarding), person(_person->__forwarding), weakPerson(_weakPerson->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};