上一篇文章iOS底層原理總結(jié) - 探尋block的本質(zhì)(一)中已經(jīng)介紹過(guò)block的底層本質(zhì)實(shí)現(xiàn)以及了解了變量的捕獲,本文繼續(xù)探尋block的本質(zhì)。
Block-Demo
快速寫(xiě)block
block對(duì)對(duì)象變量的捕獲
block一般使用過(guò)程中都是對(duì)對(duì)象變量的捕獲,那么對(duì)象變量的捕獲同基本數(shù)據(jù)類型變量相同嗎?
查看一下代碼思考:當(dāng)在block中訪問(wèn)的為對(duì)象類型時(shí),對(duì)象什么時(shí)候會(huì)銷毀?
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沒(méi)有被釋放
NSLog(@"--------");
} // person 釋放
return 0;
}
大括號(hào)執(zhí)行完畢之后,person依然不會(huì)被釋放。
上一篇文章提到過(guò),person為auto變量,傳入的block的變量同樣為person,
即block有一個(gè)強(qiáng)引用引用person,所以block不被銷毀的話,peroson也不會(huì)銷毀。 查看源代碼確實(shí)如此:

將上述代碼轉(zhuǎn)移到MRC環(huán)境下,在MRC環(huán)境下即使block還在,person卻被釋放掉了。因?yàn)镸RC環(huán)境下block在??臻g,??臻g對(duì)外面的person不會(huì)進(jìn)行強(qiáng)引用。
//MRC環(huán)境下代碼
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);
};
[person release];
} // person被釋放
NSLog(@"--------");
}
return 0;
}
block調(diào)用copy操作之后,person不會(huì)被釋放。
block = [^{
NSLog(@"------block內(nèi)部%d",person.age);
} copy];
上文中也提到過(guò),只需要對(duì)棧空間的block進(jìn)行一次copy操作,將棧空間的block拷貝到堆中,person就不會(huì)被釋放,說(shuō)明堆空間的block可能會(huì)對(duì)person進(jìn)行一次retain操作,以保證person不會(huì)被銷毀。堆空間的block自己銷毀之后也會(huì)對(duì)持有的對(duì)象進(jìn)行release操作。
也就是說(shuō):
??臻g上的block不會(huì)對(duì)對(duì)象強(qiáng)引用,堆空間的block有能力持有外部調(diào)用的對(duì)象,即對(duì)對(duì)象進(jìn)行強(qiáng)引用或去除強(qiáng)引用的操作。
__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;
}
將代碼轉(zhuǎn)化為c++來(lái)看一下上述代碼之間的差別。 __weak修飾變量,需要告知編譯器使用ARC環(huán)境及版本號(hào)否則會(huì)報(bào)錯(cuò),添加說(shuō)明-fobjc-arc -fobjc-runtime=ios-8.0.0
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

__weak修飾的變量,在生成的__main_block_impl_0中也是使用__weak修飾。
__main_block_copy_0 和 __main_block_dispose_0
當(dāng)block中捕獲對(duì)象類型的變量時(shí),我們發(fā)現(xiàn)block結(jié)構(gòu)體__main_block_impl_0的描述結(jié)構(gòu)體__main_block_desc_0中多了兩個(gè)參數(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對(duì)象的地址,person對(duì)象,以及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對(duì)象,以及8。
_Block_object_assign函數(shù)調(diào)用時(shí)機(jī)及作用
當(dāng)block進(jìn)行copy操作的時(shí)候就會(huì)自動(dòng)調(diào)用__main_block_desc_0內(nèi)部的__main_block_copy_0函數(shù),__main_block_copy_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)。
_Block_object_assign函數(shù)會(huì)自動(dòng)根據(jù)__main_block_impl_0結(jié)構(gòu)體內(nèi)部的person是什么類型的指針,對(duì)person對(duì)象產(chǎn)生強(qiáng)引用或者弱引用??梢岳斫鉃?code>_Block_object_assign函數(shù)內(nèi)部會(huì)對(duì)person進(jìn)行引用計(jì)數(shù)器的操作,如果__main_block_impl_0結(jié)構(gòu)體內(nèi)person指針是__strong類型,則為強(qiáng)引用,引用計(jì)數(shù)+1,如果__main_block_impl_0結(jié)構(gòu)體內(nèi)person指針是__weak類型,則為弱引用,引用計(jì)數(shù)不變。
_Block_object_dispose函數(shù)調(diào)用時(shí)機(jī)及作用
當(dāng)block從堆中移除時(shí)就會(huì)自動(dòng)調(diào)用__main_block_desc_0中的__main_block_dispose_0函數(shù),__main_block_dispose_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)。
_Block_object_dispose會(huì)對(duì)person對(duì)象做釋放操作,類似于release,也就是斷開(kāi)對(duì)person對(duì)象的引用,而person究竟是否被釋放還是取決于person對(duì)象自己的引用計(jì)數(shù)。
總結(jié)
- 一旦block中捕獲的變量為對(duì)象類型,
block結(jié)構(gòu)體中的__main_block_desc_0會(huì)出兩個(gè)參數(shù)copy和dispose。因?yàn)樵L問(wèn)的是個(gè)對(duì)象,block希望擁有這個(gè)對(duì)象,就需要對(duì)對(duì)象進(jìn)行引用,也就是進(jìn)行內(nèi)存管理的操作。比如說(shuō)對(duì)對(duì)象進(jìn)行retarn操作,因此一旦block捕獲的變量是對(duì)象類型就會(huì)會(huì)自動(dòng)生成copy和dispose來(lái)對(duì)內(nèi)部引用的對(duì)象進(jìn)行內(nèi)存管理。
- 一旦block中捕獲的變量為對(duì)象類型,
- 當(dāng)block內(nèi)部訪問(wèn)了對(duì)象類型的auto變量時(shí),如果block是在棧上,block內(nèi)部不會(huì)對(duì)person產(chǎn)生強(qiáng)引用。不論block結(jié)構(gòu)體內(nèi)部的變量是
__strong修飾還是__weak修飾,都不會(huì)對(duì)變量產(chǎn)生強(qiáng)引用。
- 當(dāng)block內(nèi)部訪問(wèn)了對(duì)象類型的auto變量時(shí),如果block是在棧上,block內(nèi)部不會(huì)對(duì)person產(chǎn)生強(qiáng)引用。不論block結(jié)構(gòu)體內(nèi)部的變量是
- 如果block被拷貝到堆上。
copy函數(shù)會(huì)調(diào)用_Block_object_assign函數(shù),根據(jù)auto變量的修飾符(__strong,__weak,unsafe_unretained)做出相應(yīng)的操作,形成強(qiáng)引用或者弱引用
- 如果block被拷貝到堆上。
- 如果block從堆中移除,
dispose函數(shù)會(huì)調(diào)用_Block_object_dispose函數(shù),自動(dòng)釋放引用的auto變量。
- 如果block從堆中移除,
問(wèn)題
1. 下列代碼person在何時(shí)銷毀 ?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",person);
});
NSLog(@"touchBegin----------End");
}
打印內(nèi)容

答:上文提到過(guò)ARC環(huán)境中,block作為GCD API的方法參數(shù)時(shí)會(huì)自動(dòng)進(jìn)行copy操作,因此block在堆空間,并且使用強(qiáng)引用訪問(wèn)person對(duì)象,因此block內(nèi)部copy函數(shù)會(huì)對(duì)person進(jìn)行強(qiáng)引用。當(dāng)block執(zhí)行完畢需要被銷毀時(shí),調(diào)用dispose函數(shù)釋放對(duì)person對(duì)象的引用,person沒(méi)有強(qiáng)指針指向時(shí)才會(huì)被銷毀。
2. 下列代碼person在何時(shí)銷毀 ?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
__weak Person *waekP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",waekP);
});
NSLog(@"touchBegin----------End");
}
打印內(nèi)容

答:block中對(duì)waekP為__weak弱引用,因此block內(nèi)部copy函數(shù)會(huì)對(duì)person同樣進(jìn)行弱引用,當(dāng)大括號(hào)執(zhí)行完畢時(shí),person對(duì)象沒(méi)有強(qiáng)指針引用就會(huì)被釋放。因此block塊執(zhí)行的時(shí)候打印null。
3. 通過(guò)示例代碼進(jìn)行總結(jié)。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
__weak Person * weakP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"weakP ----- %@", weakP);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"person ----- %@",person);
});
});
NSLog(@"touchBegin----------End");
}
打印內(nèi)容

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
__weak Person *weakP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"person ----- %@",person);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"weakP ----- %@",weakP);
});
});
NSLog(@"touchBegin----------End");
}
打印內(nèi)容

block內(nèi)修改變量的值
本部分分析基于下面代碼。
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
Block block = ^ {
// age = 20; // 無(wú)法修改
NSLog(@"%d",age);
};
block();
}
return 0;
}
默認(rèn)情況下block不能修改外部的局部變量。通過(guò)之前對(duì)源碼的分析可以知道。
age是在main函數(shù)內(nèi)部聲明的,說(shuō)明age的內(nèi)存存在于main函數(shù)的??臻g內(nèi)部,
但是block內(nèi)部的代碼在__main_block_func_0函數(shù)內(nèi)部。
__main_block_func_0函數(shù)內(nèi)部無(wú)法訪問(wèn)age變量的內(nèi)存空間,兩個(gè)函數(shù)的棧空間不一樣,
__main_block_func_0內(nèi)部拿到的age是block結(jié)構(gòu)體內(nèi)部的age,
因此無(wú)法在__main_block_func_0函數(shù)內(nèi)部去修改main函數(shù)內(nèi)部的變量。
方式一:age使用static修飾。(不推薦 ,這樣age會(huì)一直存在內(nèi)存中)
前文提到過(guò)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變量值的問(wèn)題,__block不能修飾靜態(tài)變量(static) 和全局變量
__block int age = 10;
編譯器會(huì)將__block修飾的變量包裝成一個(gè)對(duì)象,查看其底層c++源碼。

上述源碼中可以發(fā)現(xiàn)
首先被__block修飾的age變量聲明變?yōu)槊麨?code>age的__Block_byref_age_0結(jié)構(gòu)體,
也就是說(shuō)加上__block修飾的話捕獲到的block內(nèi)的變量為__Block_byref_age_0類型的結(jié)構(gòu)體。
通過(guò)下圖查看__Block_byref_age_0結(jié)構(gòu)體內(nèi)存儲(chǔ)哪些元素。

__isa指針 :__Block_byref_age_0中也有isa指針也就是說(shuō)__Block_byref_age_0本質(zhì)也一個(gè)對(duì)象。
__forwarding :__forwarding是__Block_byref_age_0結(jié)構(gòu)體類型的,并且__forwarding存儲(chǔ)的值為(__Block_byref_age_0 *)&age,即結(jié)構(gòu)體自己的內(nèi)存地址。
__flags :0
__size :sizeof(__Block_byref_age_0)即__Block_byref_age_0所占用的內(nèi)存空間。
age :真正存儲(chǔ)變量的地方,這里存儲(chǔ)局部變量10。
接著將__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,通過(guò)age結(jié)構(gòu)體拿到__forwarding指針,上面提到過(guò)__forwarding中保存的就是__Block_byref_age_0結(jié)構(gòu)體本身,這里也就是age(__Block_byref_age_0),在通過(guò)__forwarding拿到結(jié)構(gòu)體中的age(10)變量并修改其值。
后續(xù)NSLog中使用age時(shí)也通過(guò)同樣的方式獲取age的值。

為什么要通過(guò)__forwarding獲取age變量的值?
__forwarding是指向自己的指針。這樣的做法是為了方便內(nèi)存管理,之后內(nèi)存管理章節(jié)會(huì)詳細(xì)解釋。
到此為止,__block為什么能修改變量的值已經(jīng)很清晰了。
__block將變量包裝成對(duì)象,然后在把age封裝在結(jié)構(gòu)體里面,block內(nèi)部存儲(chǔ)的變量為結(jié)構(gòu)體指針,也就可以通過(guò)指針找到內(nèi)存地址進(jìn)而修改變量的值。
__block修飾對(duì)象類型
那么如果變量本身就是對(duì)象類型呢?通過(guò)以下代碼生成c++源碼查看
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;
}
通過(guò)源碼查看,將對(duì)象包裝在一個(gè)新的結(jié)構(gòu)體中。結(jié)構(gòu)體內(nèi)部會(huì)有一個(gè)person對(duì)象,不一樣的地方是結(jié)構(gòu)體內(nèi)部添加了內(nèi)存管理的兩個(gè)函數(shù)__Block_byref_id_object_copy和__Block_byref_id_object_dispose

__Block_byref_id_object_copy和__Block_byref_id_object_dispose函數(shù)的調(diào)用時(shí)機(jī)及作用在__block內(nèi)存管理部分詳細(xì)分析。
問(wèn)題
1. 以下代碼是否可以正確執(zhí)行
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
Block block = ^{
[array addObject: @"5"];
[array addObject: @"5"];
NSLog(@"%@",array);
};
block();
}
return 0;
}
答:可以正確執(zhí)行,因?yàn)樵赽lock塊中僅僅是使用了array的內(nèi)存地址,往內(nèi)存地址中添加內(nèi)容,并沒(méi)有修改arry的內(nèi)存地址,因此array不需要使用__block修飾也可以正確編譯。
因此當(dāng)僅僅是使用局部變量的內(nèi)存地址,而不是修改的時(shí)候,盡量不要添加__block,通過(guò)上述分析我們知道一旦添加了__block修飾符,系統(tǒng)會(huì)自動(dòng)創(chuàng)建相應(yīng)的結(jié)構(gòu)體,占用不必要的內(nèi)存空間。
2. 上面提到過(guò)__block修飾的age變量在編譯時(shí)會(huì)被封裝為結(jié)構(gòu)體,那么當(dāng)在外部使用age變量的時(shí)候,使用的是__Block_byref_age_0結(jié)構(gòu)體呢?還是__Block_byref_age_0結(jié)構(gòu)體內(nèi)的age變量呢?
為了驗(yàn)證上述問(wèn)題 同樣使用自定義結(jié)構(gòu)體的方式來(lái)查看其內(nèi)部結(jié)構(gòu)
typedef void (^Block)(void);
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age; // by ref
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
Block block = ^{
age = 20;
NSLog(@"age is %d",age);
};
block();
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%p",&age);
}
return 0;
}
打印斷點(diǎn)查看結(jié)構(gòu)體內(nèi)部結(jié)構(gòu)

通過(guò)查看blockImpl結(jié)構(gòu)體其中的內(nèi)容,找到age結(jié)構(gòu)體,其中重點(diǎn)觀察兩個(gè)元素:
-
__forwarding其中存儲(chǔ)的地址確實(shí)是age結(jié)構(gòu)體變量自己的地址 -
age中存儲(chǔ)這修改后的變量20。

上面也提到過(guò),在block中使用或修改age的時(shí)候都是通過(guò)結(jié)構(gòu)體__Block_byref_age_0找到__forwarding在找到變量age的。
另外apple為了隱藏__Block_byref_age_0結(jié)構(gòu)體的實(shí)現(xiàn),打印age變量的地址發(fā)現(xiàn)其實(shí)是__Block_byref_age_0結(jié)構(gòu)體內(nèi)age變量的地址。

通過(guò)上圖的計(jì)算可以發(fā)現(xiàn)打印age的地址同__Block_byref_age_0結(jié)構(gòu)體內(nèi)age值的地址相同。也就是說(shuō)外面使用的age,代表的就是結(jié)構(gòu)體內(nèi)的age值。所以直接拿來(lái)用的age就是之前聲明的int age。
__block內(nèi)存管理
上文提到當(dāng)block中捕獲對(duì)象類型的變量時(shí),block中的__main_block_desc_0結(jié)構(gòu)體內(nèi)部會(huì)自動(dòng)添加copy和dispose函數(shù)對(duì)捕獲的變量進(jìn)行內(nèi)存管理。
那么同樣的當(dāng)block內(nèi)部捕獲__block修飾的對(duì)象類型的變量時(shí),__Block_byref_person_0結(jié)構(gòu)體內(nèi)部也會(huì)自動(dòng)添加__Block_byref_id_object_copy和__Block_byref_id_object_dispose對(duì)被__block包裝成結(jié)構(gòu)體的對(duì)象進(jìn)行內(nèi)存管理。
當(dāng)block內(nèi)存在棧上時(shí),并不會(huì)對(duì)__block變量產(chǎn)生內(nèi)存管理。當(dāng)blcok被copy到堆上時(shí) 會(huì)調(diào)用block內(nèi)部的copy函數(shù),copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù),_Block_object_assign函數(shù)會(huì)對(duì)__block變量形成強(qiáng)引用(相當(dāng)于retain)
首先通過(guò)一張圖看一下block復(fù)制到堆上時(shí)內(nèi)存變化

當(dāng)block被copy到堆上時(shí),block內(nèi)部引用的__block變量也會(huì)被復(fù)制到堆上,并且持有變量,如果block復(fù)制到堆上的同時(shí),__block變量已經(jīng)存在堆上了,則不會(huì)復(fù)制。
當(dāng)block從堆中移除的話,就會(huì)調(diào)用dispose函數(shù),也就是__main_block_dispose_0函數(shù),__main_block_dispose_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù),會(huì)自動(dòng)釋放引用的__block變量。

block內(nèi)部決定什么時(shí)候?qū)⒆兞繌?fù)制到堆中,什么時(shí)候?qū)ψ兞孔鲆糜?jì)數(shù)的操作。
__block修飾的變量在block結(jié)構(gòu)體中一直都是強(qiáng)引用,而其他類型的是由傳入的對(duì)象指針類型決定。
一段代碼更深入的觀察一下。
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); // 對(duì)象類型的局部變量
NSLog(@"%p",weakObj); // __weak修飾的對(duì)象類型的局部變量
NSLog(@"%p",person); // __block修飾的對(duì)象類型的局部變量
NSLog(@"%p",weakPerson); // __block,__weak修飾的對(duì)象類型的局部變量
};
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;
}
};
上述__main_block_impl_0結(jié)構(gòu)體中看出,沒(méi)有使用__block修飾的變量(object 和 weadObj)則根據(jù)他們本身被block捕獲的指針類型對(duì)他們進(jìn)行強(qiáng)引用或弱引用,而一旦使用__block修飾的變量,__main_block_impl_0結(jié)構(gòu)體內(nèi)一律使用強(qiáng)指針引用生成的結(jié)構(gòu)體。
接著我們來(lái)看__block修飾的變量生成的結(jié)構(gòu)體有什么不同
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
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;
};
struct __Block_byref_weakPerson_2 {
void *__isa;
__Block_byref_weakPerson_2 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__weak weakPerson;
};
如上面分析的那樣,__block修飾對(duì)象類型的變量生成的結(jié)構(gòu)體內(nèi)部多了__Block_byref_id_object_copy和__Block_byref_id_object_dispose兩個(gè)函數(shù),用來(lái)對(duì)對(duì)象類型的變量進(jìn)行內(nèi)存管理的操作。而結(jié)構(gòu)體對(duì)對(duì)象的引用類型,則取決于block捕獲的對(duì)象類型的變量。weakPerson是弱指針,所以__Block_byref_weakPerson_2對(duì)weakPerson就是弱引用,person是強(qiáng)指針,所以__Block_byref_person_1對(duì)person就是強(qiáng)引用。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->object, (void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->weakObj, (void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
}
__main_block_copy_0函數(shù)中會(huì)根據(jù)變量是強(qiáng)弱指針及有沒(méi)有被__block修飾做出不同的處理,強(qiáng)指針在block內(nèi)部產(chǎn)生強(qiáng)引用,弱指針在block內(nèi)部產(chǎn)生弱引用。被__block修飾的變量最后的參數(shù)傳入的是8,沒(méi)有被__block修飾的變量最后的參數(shù)傳入的是3。
當(dāng)block從堆中移除時(shí)通過(guò)dispose函數(shù)來(lái)釋放他們。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
}
__forwarding指針
上面提到過(guò)__forwarding指針指向的是結(jié)構(gòu)體自己。當(dāng)使用變量的時(shí)候,通過(guò)結(jié)構(gòu)體找到__forwarding指針,在通過(guò)__forwarding指針找到相應(yīng)的變量。這樣設(shè)計(jì)的目的是為了方便內(nèi)存管理。通過(guò)上面對(duì)__block變量的內(nèi)存管理分析我們知道,block被復(fù)制到堆上時(shí),會(huì)將block中引用的變量也復(fù)制到堆中。
我們重回到源碼中。當(dāng)在block中修改__block修飾的變量時(shí)。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_jm_dztwxsdn7bvbz__xj2vlp8980000gn_T_main_b05610_mi_0,(age->__forwarding->age));
}
通過(guò)源碼可以知道,當(dāng)修改__block修飾的變量時(shí),是根據(jù)變量生成的結(jié)構(gòu)體這里是__Block_byref_age_0找到其中__forwarding指針,__forwarding指針指向的是結(jié)構(gòu)體自己因此可以找到age變量進(jìn)行修改。
當(dāng)block在棧中時(shí),__Block_byref_age_0結(jié)構(gòu)體內(nèi)的__forwarding指針指向結(jié)構(gòu)體自己。
而當(dāng)block被復(fù)制到堆中時(shí),棧中的__Block_byref_age_0結(jié)構(gòu)體也會(huì)被復(fù)制到堆中一份,而此時(shí)棧中的__Block_byref_age_0結(jié)構(gòu)體中的__forwarding指針指向的就是堆中的__Block_byref_age_0結(jié)構(gòu)體,堆中__Block_byref_age_0結(jié)構(gòu)體內(nèi)的__forwarding指針依然指向自己。
此時(shí)當(dāng)對(duì)age進(jìn)行修改時(shí)
// 棧中的age
__Block_byref_age_0 *age = __cself->age; // bound by ref
// age->__forwarding獲取堆中的age結(jié)構(gòu)體
// age->__forwarding->age 修改堆中age結(jié)構(gòu)體的age變量
(age->__forwarding->age) = 20;
通過(guò)__forwarding指針巧妙的將修改的變量賦值在堆中的__Block_byref_age_0中。
我們通過(guò)一張圖展示__forwarding指針的作用

因此block內(nèi)部拿到的變量實(shí)際就是在堆上的。當(dāng)block進(jìn)行copy被復(fù)制到堆上時(shí),_Block_object_assign函數(shù)內(nèi)做的這一系列操作。
被__block修飾的對(duì)象類型的內(nèi)存管理
使用以下代碼,生成c++代碼查看內(nèi)部實(shí)現(xiàn)
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
Block block = ^ {
NSLog(@"%p", person);
};
block();
}
return 0;
}
來(lái)到源碼查看__Block_byref_person_0結(jié)構(gòu)體及其聲明
__Block_byref_person_0結(jié)構(gòu)體
typedef void (*Block)(void);
struct __Block_byref_person_0 {
void *__isa; // 8 內(nèi)存空間
__Block_byref_person_0 *__forwarding; // 8
int __flags; // 4
int __size; // 4
void (*__Block_byref_id_object_copy)(void*, void*); // 8
void (*__Block_byref_id_object_dispose)(void*); // 8
Person *__strong person; // 8
};
// 8 + 8 + 4 + 4 + 8 + 8 + 8 = 48
// __Block_byref_person_0結(jié)構(gòu)體聲明
__attribute__((__blocks__(byref))) __Block_byref_person_0 person = {
(void*)0,
(__Block_byref_person_0 *)&person,
33554432,
sizeof(__Block_byref_person_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))
};
之前提到過(guò)__block修飾的對(duì)象類型生成的結(jié)構(gòu)體中新增加了兩個(gè)函數(shù)void (*__Block_byref_id_object_copy)(void*, void*);和void (*__Block_byref_id_object_dispose)(void*);。這兩個(gè)函數(shù)為__block修飾的對(duì)象提供了內(nèi)存管理的操作。
可以看出為void (*__Block_byref_id_object_copy)(void*, void*);和void (*__Block_byref_id_object_dispose)(void*);賦值的分別為__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131。找到這兩個(gè)函數(shù)
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);
}
上述源碼中可以發(fā)現(xiàn)__Block_byref_id_object_copy_131函數(shù)中同樣調(diào)用了_Block_object_assign函數(shù),而_Block_object_assign函數(shù)內(nèi)部拿到dst指針即block對(duì)象自己的地址值加上40個(gè)字節(jié)。并且_Block_object_assign最后傳入的參數(shù)是131,同block直接對(duì)對(duì)象進(jìn)行內(nèi)存管理傳入的參數(shù)3,8都不同??梢圆孪?code>_Block_object_assign內(nèi)部根據(jù)傳入的參數(shù)不同進(jìn)行不同的操作的。
通過(guò)對(duì)上面__Block_byref_person_0結(jié)構(gòu)體占用空間計(jì)算發(fā)現(xiàn)__Block_byref_person_0結(jié)構(gòu)體占用的空間為48個(gè)字節(jié)。而加40恰好指向的就為person指針。
也就是說(shuō)copy函數(shù)會(huì)將person地址傳入_Block_object_assign函數(shù),_Block_object_assign中對(duì)Person對(duì)象進(jìn)行強(qiáng)引用或者弱引用。

如果使用__weak修飾變量查看一下其中的源碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
__block __weak Person *weakPerson = person;
Block block = ^ {
NSLog(@"%p", weakPerson);
};
block();
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakPerson_0 *weakPerson; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0中沒(méi)有任何變化,__main_block_impl_0對(duì)weakPerson依然是強(qiáng)引用,但是__Block_byref_weakPerson_0中對(duì)weakPerson變?yōu)榱?code>__weak指針。
struct __Block_byref_weakPerson_0 {
void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__weak weakPerson;
};
也就是說(shuō)無(wú)論如何block內(nèi)部中對(duì)__block修飾變量生成的結(jié)構(gòu)體都是強(qiáng)引用,結(jié)構(gòu)體內(nèi)部對(duì)外部變量的引用取決于傳入block內(nèi)部的變量是強(qiáng)引用還是弱引用。

mrc環(huán)境下,盡管調(diào)用了copy操作,__block結(jié)構(gòu)體不會(huì)對(duì)person產(chǎn)生強(qiáng)引用,依然是弱引用。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
Block block = [^ {
NSLog(@"%p", person);
} copy];
[person release];
block();
[block release];
}
return 0;
}
上述代碼person會(huì)先釋放
block的copy[50480:8737001] -[Person dealloc]
block的copy[50480:8737001] 0x100669a50
當(dāng)block從堆中移除的時(shí)候。會(huì)調(diào)用dispose函數(shù),block塊中去除對(duì)__Block_byref_person_0 *person;的引用,__Block_byref_person_0結(jié)構(gòu)體中也會(huì)調(diào)用dispose操作去除對(duì)Person *person;的引用。以保證結(jié)構(gòu)體和結(jié)構(gòu)體內(nèi)部的對(duì)象可以正常釋放。
循環(huán)引用
循環(huán)引用導(dǎo)致內(nèi)存泄漏。
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"%d",person.age);
};
}
NSLog(@"大括號(hào)結(jié)束啦");
return 0;
}
運(yùn)行代碼打印內(nèi)容
block的copy[55423:9158212] 大括號(hào)結(jié)束啦
可以發(fā)現(xiàn)大括號(hào)結(jié)束之后,person依然沒(méi)有被釋放,產(chǎn)生了循環(huán)引用。
通過(guò)一張圖看一下他們之間的內(nèi)存結(jié)構(gòu)

上圖中可以發(fā)現(xiàn),Person對(duì)象和block對(duì)象相互之間產(chǎn)生了強(qiáng)引用,導(dǎo)致雙方都不會(huì)被釋放,進(jìn)而造成內(nèi)存泄漏。
解決循環(huán)引用問(wèn)題 - ARC
首先為了能隨時(shí)執(zhí)行block,我們肯定希望person對(duì)block對(duì)強(qiáng)引用,而block內(nèi)部對(duì)person的引用為弱引用最好。
使用__weak 和 __unsafe_unretained修飾符可以解決循環(huán)引用的問(wèn)題
我們上面也提到過(guò)__weak會(huì)使block內(nèi)部將指針變?yōu)槿踔羔槨?code>block對(duì)person對(duì)象為弱指針的話,也就不會(huì)出現(xiàn)相互引用而導(dǎo)致不會(huì)被釋放了。

__weak 和 __unsafe_unretained的區(qū)別。
__weak不會(huì)產(chǎn)生強(qiáng)引用,指向的對(duì)象銷毀時(shí),會(huì)自動(dòng)將指針置為nil。因此一般通過(guò)__weak來(lái)解決問(wèn)題。
__unsafe_unretained不會(huì)產(chǎn)生前引用,不安全,指向的對(duì)象銷毀時(shí),指針存儲(chǔ)的地址值不變。
使用__block也可以解決循環(huán)引用的問(wèn)題。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"%d",person.age);
person = nil;
};
person.block();
}
NSLog(@"大括號(hào)結(jié)束啦");
return 0;
}
上述代碼之間的相互引用可以使用下圖表示

上面我們提到過(guò),在block內(nèi)部使用變量使用的其實(shí)是__block修飾的變量生成的結(jié)構(gòu)體__Block_byref_person_0內(nèi)部的person對(duì)象,那么當(dāng)person對(duì)象置為nil也就斷開(kāi)了結(jié)構(gòu)體對(duì)person的強(qiáng)引用,那么三角的循環(huán)引用就自動(dòng)斷開(kāi)。該釋放的時(shí)候就會(huì)釋放了。但是有弊端,必須執(zhí)行block,并且在block內(nèi)部將person對(duì)象置為nil。也就是說(shuō)在block執(zhí)行之前代碼是因?yàn)檠h(huán)引用導(dǎo)致內(nèi)存泄漏的。
解決循環(huán)引用問(wèn)題 - MRC
使用__unsafe_unretained解決。在MRC環(huán)境下不支持使用__weak,使用原理同ARC環(huán)境下相同,這里不在贅述。
使用__block也能解決循環(huán)引用的問(wèn)題。因?yàn)樯衔?code>__block內(nèi)存管理中提到過(guò),MRC環(huán)境下,盡管調(diào)用了copy操作,__block結(jié)構(gòu)體不會(huì)對(duì)person產(chǎn)生強(qiáng)引用,依然是弱引用。因此同樣可以解決循環(huán)引用的問(wèn)題。
__strong 和 __weak
__weak typeof(self) weakSelf = self;
person.block = ^{
__strong typeof(weakSelf) myself = weakSelf;
NSLog(@"age is %d", myself->_age);
};
在block內(nèi)部重新使用__strong修飾self變量是為了在block內(nèi)部有一個(gè)強(qiáng)指針指向weakSelf避免在block調(diào)用的時(shí)候weakSelf已經(jīng)被銷毀。