面試題引發(fā)的思考:
Q: block的屬性修飾詞為什么是copy
?
- 如果block是在棧上,將不會(huì)對(duì)
auto
變量產(chǎn)生強(qiáng)引用智嚷; -
如果block被
copy
到堆上:
1> 會(huì)調(diào)用block內(nèi)部的copy
函數(shù);
2>copy
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign
函數(shù)纺且;
3>_Block_object_assign
函數(shù)會(huì)根據(jù)auto
變量的修飾符(__strong
盏道,_weak
)做出相應(yīng)的操作,形成強(qiáng)引用隆檀、弱引用(僅限于ARC時(shí)會(huì)retain
摇天,MRC時(shí)不會(huì))。 -
如果block從堆上移除:
1> 會(huì)調(diào)用block內(nèi)部的dispose
函數(shù)恐仑;
2>dispose
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose
函數(shù)泉坐;
3>_Block_object_dispose
函數(shù)會(huì)自動(dòng)釋放引用的auto
變量,類(lèi)似于release
裳仆。
對(duì)象類(lèi)型的auto
變量
iOS底層原理 - 探尋block本質(zhì)(一)中介紹到block底層原理以及block的變量捕獲腕让,那么block對(duì)對(duì)象類(lèi)型變量的捕獲同對(duì)基本數(shù)據(jù)類(lèi)型變量的捕獲是否相同?
(1) 椘缯澹空間纯丸、堆空間的強(qiáng)引用、弱引用
Q: 首先查看一下代碼静袖,block訪(fǎng)問(wèn)對(duì)象類(lèi)型變量時(shí)觉鼻,對(duì)象何時(shí)銷(xiāo)毀?
// TODO: ----------------- Person類(lèi) -----------------
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end
@implementation Person
- (void)dealloc {
NSLog(@"------------ Person - dealloc");
}
@end
// TODO: ----------------- main -----------------
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"------------ 內(nèi)部 %d", person.age);
};
} // 執(zhí)行完畢队橙,person沒(méi)有被釋放
NSLog(@"------------ 外部");
}
return 0;
}
// 打印結(jié)果
Demo[1234:567890] ------------ 外部
Demo[1234:567890] ------------ Person - dealloc
由打印結(jié)果可知:
大括號(hào)執(zhí)行完畢坠陈,person
沒(méi)有被釋放。
而person
對(duì)象是在大括號(hào)內(nèi)聲明的局部變量捐康,它的生命周期僅限于這個(gè)大括號(hào)內(nèi)仇矾,打印結(jié)果是什么原因造成的?
上篇文章介紹到:person
是auto
變量解总,person
會(huì)被捕獲到block內(nèi)部贮匕,即block對(duì)person
進(jìn)行強(qiáng)引用; 那么block銷(xiāo)毀之前花枫,person
是不會(huì)被銷(xiāo)毀的刻盐。
查看源碼掏膏,符合我們的結(jié)論。
下面我們進(jìn)入MRC環(huán)境:
// MRC環(huán)境
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"------------ 內(nèi)部 %d", person.age);
};
[person release];
} // 執(zhí)行完畢隙疚,person被釋放
NSLog(@"------------ 外部");
}
return 0;
}
// 打印結(jié)果
Demo[1234:567890] ------------ Person - dealloc
Demo[1234:567890] ------------ 外部
由打印結(jié)果可知:
大括號(hào)執(zhí)行完畢壤追,person
被釋放。
原因是:在MRC環(huán)境下供屉,block訪(fǎng)問(wèn)auto
變量,會(huì)在椖缃叮空間伶丐,不會(huì)對(duì)person
進(jìn)行強(qiáng)引用。
對(duì)block進(jìn)行copy
操作疯特,person
沒(méi)有被釋放
// MRC環(huán)境
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = [^{
NSLog(@"------------ 內(nèi)部 %d", person.age);
} copy];
[person release];
}
// 執(zhí)行完畢哗魂,person沒(méi)有被釋放
NSLog(@"------------ 外部");
// 需要block銷(xiāo)毀掉,person才會(huì)被釋放
[block release];
}
return 0;
}
// 打印結(jié)果
Demo[1234:567890] ------------ 外部
Demo[1234:567890] ------------ Person - dealloc
由打印結(jié)果可知:
大括號(hào)執(zhí)行完畢漓雅,person
沒(méi)有被釋放录别。
原因是:對(duì)棧空間的block進(jìn)行copy
操作邻吞,將椬樘猓空間的block拷貝到堆中,會(huì)對(duì)person
進(jìn)行強(qiáng)引用抱冷;
堆空間的block可能會(huì)對(duì)person
進(jìn)行一次retain
操作崔列,保證person
不被銷(xiāo)毀,堆空間的block銷(xiāo)毀之后會(huì)對(duì)person
進(jìn)行release
操作旺遮。
總結(jié)可知:堆區(qū)的block對(duì)person
對(duì)象有強(qiáng)引用作用赵讯,棧空間的block對(duì)person
對(duì)象沒(méi)有強(qiáng)引用作用耿眉。
(2) 關(guān)鍵字__weak
切回ARC環(huán)境边翼,執(zhí)行下列代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *weakPerson = person;
block = ^{
NSLog(@"------------ 內(nèi)部 %d", weakPerson.age);
};
}
// 執(zhí)行完畢,person沒(méi)有被釋放
NSLog(@"------------ 外部");
}
return 0;
}
// 打印結(jié)果
Demo[1234:567890] ------------ Person - dealloc
Demo[1234:567890] ------------ 外部
將代碼轉(zhuǎn)化成C++鸣剪,_weak
修飾變量摸柄,需要告知編譯器使用ARC環(huán)境及版本號(hào):
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
由源碼可知:_weak
修飾的變量洛搀,在生成的__main_block_impl_0
中也是使用_weak
修飾。
所以對(duì)person
對(duì)象是弱引用,不能改變person
對(duì)象的生命周期鞍盗。
(3) _main_block_copy_0
函數(shù)和 __main_block_dispose_0
函數(shù)
block捕獲對(duì)象類(lèi)型的變量時(shí)__main_block_impl_0
內(nèi)部結(jié)構(gòu)體__main_block_desc_0
中多了copy
函數(shù)和dispose
函數(shù)兩個(gè)參數(shù):
copy
函數(shù)和dispose
函數(shù)中傳入的都是__main_block_impl_0
結(jié)構(gòu)體本身赁咙。
a> copy
函數(shù)本質(zhì)是__main_block_copy_0
函數(shù)今野,其內(nèi)部調(diào)用_Block_object_assign
函數(shù);
_Block_object_assign
中傳入的是person
對(duì)象的地址饺鹃,person
對(duì)象莫秆,以及8
间雀。
b> dispose
函數(shù)本質(zhì)是__main_block_dispose_0
函數(shù),其內(nèi)部調(diào)用_Block_object_dispose
函數(shù)镊屎;
_Block_object_dispose
函數(shù)傳入的參數(shù)是person
對(duì)象惹挟,以及8
。
1> _Block_object_assign
函數(shù)
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
的指針類(lèi)型,對(duì)person
對(duì)象產(chǎn)生強(qiáng)引用或者弱引用用狱。
2> _Block_object_dispose
函數(shù)
當(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ì)象做釋放操作夏伊,類(lèi)似于release
摇展,即斷開(kāi)對(duì)person
對(duì)象的引用,而person
究竟是否被釋放還是取決于person
對(duì)象自己的引用計(jì)數(shù)溺忧。
總結(jié)可知:
當(dāng)block內(nèi)部訪(fǎng)問(wèn)了對(duì)象類(lèi)型的
auto
變量時(shí):
- 如果block是在棧上咏连,將不會(huì)對(duì)
auto
變量產(chǎn)生強(qiáng)引用;- 如果block被拷貝到堆上:
1> 會(huì)調(diào)用block內(nèi)部的copy
函數(shù)鲁森;
2>copy
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign
函數(shù)祟滴;
3>_Block_object_assign
函數(shù)會(huì)根據(jù)auto
變量的修飾符(__strong
,_weak
)做出相應(yīng)的操作刀森,形成強(qiáng)引用踱启,弱引用。- 如果block從堆上移除:
1> 會(huì)調(diào)用block內(nèi)部的dispose
函數(shù)研底;
2>dispose
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose
函數(shù)埠偿;
3>_Block_object_dispose
函數(shù)會(huì)自動(dòng)釋放引用的auto
變量,類(lèi)似于release
榜晦。
(4) 以下幾個(gè)示例中person
都在何時(shí)銷(xiāo)毀冠蒋?
1> 示例一:
- (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(@"------------ touchesBegan");
}
打印結(jié)果顯示:block執(zhí)行完畢后person
對(duì)象銷(xiāo)毀乾胶。
由iOS底層原理 - 探尋block本質(zhì)(一)可知:
ARC環(huán)境中抖剿,block作為GCD API的方法參數(shù)時(shí)會(huì)自動(dòng)進(jìn)行copy
操作斩郎,將block復(fù)制到堆上,所以block內(nèi)部copy
函數(shù)會(huì)對(duì)person
進(jìn)行強(qiáng)引用喻频。
當(dāng)block執(zhí)行完畢需要銷(xiāo)毀時(shí)缩宜,調(diào)用dispose
函數(shù)釋放對(duì)person
對(duì)象的引用,person
沒(méi)有強(qiáng)指針引用時(shí)會(huì)被銷(xiāo)毀锻煌。
2> 示例二:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
Person *person = [[Person alloc] init];
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"------------ %@", weakPerson);
});
NSLog(@"------------ touchesBegan");
}
打印結(jié)果顯示:person
對(duì)象先銷(xiāo)毀,然后執(zhí)行block宋梧,打印為null
匣沼。
block對(duì)weakPerson
為__weak
弱引用,所以block內(nèi)部copy
函數(shù)會(huì)對(duì)person
進(jìn)行弱引用释涛。
當(dāng)touchesBegan: withEvent:
執(zhí)行完畢時(shí)倦沧,person
沒(méi)有強(qiáng)指針引用時(shí)會(huì)被銷(xiāo)毀,所以block執(zhí)行時(shí)打印null
。
3> 示例三:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
Person *person = [[Person alloc] init];
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"------------1 %@", person);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"------------2 %@", weakPerson);
});
});
NSLog(@"------------ touchesBegan");
}
打印結(jié)果顯示:外層block執(zhí)行完畢后愈污,即1秒后person
對(duì)象銷(xiāo)毀轮傍,然后執(zhí)行內(nèi)層block,即3秒后打印為null
创夜。
外層block對(duì)person
進(jìn)行強(qiáng)引用,內(nèi)層block對(duì)person
進(jìn)行弱引用涧尿。
當(dāng)外層block執(zhí)行完畢時(shí)檬贰,person
有強(qiáng)指針引用;然后內(nèi)層block執(zhí)行時(shí)person
沒(méi)有強(qiáng)指針會(huì)被銷(xiāo)毀桥言,所以?xún)?nèi)層block執(zhí)行時(shí)打印null
葵礼。
4> 示例四:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
Person *person = [[Person alloc] init];
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"------------1 %@", weakPerson);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"------------2 %@", person);
});
});
NSLog(@"------------ touchesBegan");
}
打印結(jié)果顯示:外層block執(zhí)行完畢后鸳粉,然后執(zhí)行內(nèi)層block扔涧,3秒后person
對(duì)象銷(xiāo)毀赁严。
外層block對(duì)person
進(jìn)行弱引用粉铐,內(nèi)層block對(duì)person
進(jìn)行強(qiáng)引用卤档。
而person
的強(qiáng)引用何時(shí)結(jié)束劝枣,person
何時(shí)dealloc
,所以3秒后person
對(duì)象才銷(xiāo)毀舔腾。