值傳遞&&引用傳遞
首先從函數(shù)談起,函數(shù)參數(shù)傳遞的類(lèi)型分為值傳遞和引用傳遞兩種恬惯,
值傳遞的過(guò)程指的是在實(shí)參給形參賦值的過(guò)程中向拆,函數(shù)會(huì)為形參開(kāi)辟一塊新的內(nèi)存用于存儲(chǔ)實(shí)參的值,而對(duì)于引用傳遞來(lái)說(shuō),函數(shù)不會(huì)為形參開(kāi)辟一塊新的內(nèi)存酪耳,形參指向?qū)崊⒌膬?nèi)存亲铡,下面通過(guò)C++的例子來(lái)說(shuō)明。
1 值傳遞
我們新建了一個(gè)名為Person
的類(lèi) 實(shí)現(xiàn)了一個(gè)testWithNumber
的函數(shù),在函數(shù)內(nèi)打印了形參的地址
void Person::testWithNumber(int number){
cout << "函數(shù)內(nèi)" << &number << endl;
}
函數(shù)外的調(diào)用代碼如下
int realNumber = 10;
cout << "函數(shù)外" << &realNumber << endl;
p.testWithNumber(number);
此時(shí)實(shí)參 realNumber 的值10傳遞給形參number奖蔓,函數(shù)會(huì)為形參開(kāi)辟一塊新的內(nèi)存用于存儲(chǔ)實(shí)參的值10赞草,此時(shí)可以參考控制臺(tái)的打印
函數(shù)外0x7ffeefbff510
函數(shù)內(nèi)0x7ffeefbff4d4
此時(shí)如果修改形參number的值 并不會(huì)影響realNumber的值
2 引用傳遞
我們對(duì)上面的函數(shù)加以改造,C++中使用 &argument 來(lái)表示參數(shù)的引用傳遞
void Person::testWithNumber(int &number){
cout << "函數(shù)內(nèi)" << &number << endl;
}
函數(shù)外的調(diào)用代碼不變 此時(shí)控制臺(tái)打印如下
函數(shù)外0x7ffeefbff510
函數(shù)內(nèi)0x7ffeefbff510
可以看出吆鹤,對(duì)于引用傳遞來(lái)說(shuō),函數(shù)不會(huì)為形參開(kāi)辟一塊新的內(nèi)存厨疙,形參指向?qū)崊⒌膬?nèi)存,如果在函數(shù)內(nèi)修改形參number的值疑务,會(huì)影響到實(shí)參realNumber的值
Block中的__strong與__weak
block可以截獲自動(dòng)變量的值沾凄,所以block類(lèi)似于上面所說(shuō)的值傳遞
的過(guò)程。
所以下面代碼顯然會(huì)導(dǎo)致循環(huán)引用
NSLog(@"block之前 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
self.block = ^{
NSLog(@"%@",self);
};
NSLog(@"block之后 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
self持有block知允,因?yàn)樵赽lock中使用了self撒蟀,block會(huì)實(shí)例化一個(gè)__strong修飾的指針指向self 從而導(dǎo)致循環(huán)引用,打印引用計(jì)數(shù)如下(尚不清楚為什么增加了2)
2019-06-22 13:44:41.451490+0800 newtest[21910:3687326] block之前 7
2019-06-22 13:44:41.451541+0800 newtest[21910:3687326] block之后 9
為了解決循環(huán)引用問(wèn)題,經(jīng)典的做法如下
NSLog(@"block之前 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
__weak typeof(self) wself = self;
self.block = ^{
NSLog(@"%@",wself);
};
NSLog(@"block之后 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
self持有block温鸽,因?yàn)樵赽lock中使用了wself保屯,block會(huì)實(shí)例化一個(gè)__weak修飾的指針指向self,因?yàn)閣eak指針并不會(huì)增加self的引用計(jì)數(shù),從而避免了循環(huán)引用的問(wèn)題涤垫。打印引用計(jì)數(shù)如下
2019-06-22 13:55:43.660759+0800 newtest[21922:3689103] block之前 7
2019-06-22 13:55:43.660810+0800 newtest[21922:3689103] block之后 7
雖然循環(huán)引用就打破了姑尺,但是新的問(wèn)題又來(lái)了。那就是會(huì)有self提前于block執(zhí)行之前釋放的場(chǎng)景蝠猬,testBlock實(shí)體釋放了切蟋,self就指向nil了,wself也會(huì)被置為nil榆芦,等block回來(lái)時(shí)柄粹,其實(shí)在向一個(gè)nil發(fā)消息。
這時(shí)候就到了__strong登場(chǎng)了匆绣,先通過(guò)一個(gè)例子看一下__strong是如何發(fā)揮作用的
__strong YZView *view1 = [[YZView alloc] init];
NSLog(@"retain count is %ld",CFGetRetainCount((__bridge CFTypeRef) view1));
__weak YZView *view2 = view1;
NSLog(@"retain count is %ld",CFGetRetainCount((__bridge CFTypeRef) view1));
__strong YZView *view3 = view2;
NSLog(@"retain count is %ld",CFGetRetainCount((__bridge CFTypeRef) view1));
打印結(jié)果如下
2019-06-22 14:49:16.097877+0800 newtest[22002:3697668] retain count is 1
2019-06-22 14:49:16.097895+0800 newtest[22002:3697668] retain count is 1
2019-06-22 14:49:16.097908+0800 newtest[22002:3697668] retain count is 2
所以我們把__strong應(yīng)用到blcok來(lái)保證self不會(huì)被提前釋放
NSLog(@"block之前 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
__weak typeof(self) wself = self;
self.block = ^{
__strong typeof(wself) sself = wself;
NSLog(@"%@",sself);
};
NSLog(@"block之后 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
但這其實(shí)是一種錯(cuò)誤的使用方法,并沒(méi)有增加self的引用計(jì)數(shù)驻右,所以無(wú)法阻止self提前于block釋放,引用計(jì)數(shù)打印如下
2019-06-22 14:56:58.031208+0800 newtest[22005:3698610] block之前 7
2019-06-22 14:56:58.031262+0800 newtest[22005:3698610] block之后 7
原因在于sself并非blcok捕獲的外部變量犬绒,而是在block中內(nèi)部生成的局部變量旺入,所以sself只有在block實(shí)際調(diào)用的時(shí)候才會(huì)被賦予wself的值(此時(shí)wself可能是nil),并且增加self的引用計(jì)數(shù)兑凿,而在block調(diào)用結(jié)束后釋放sself凯力,并且減少self的引用計(jì)數(shù),這個(gè)臨時(shí)產(chǎn)生的“循環(huán)引用”就會(huì)被自動(dòng)打破礼华。由此可見(jiàn)咐鹤,同步返回的block的__strong雖然不會(huì)導(dǎo)致循環(huán)引用,但是并不會(huì)起作用圣絮。
所以__strong只會(huì)對(duì)異步返回的block起作用
__weak typeof(self) wself = self;
self.block = ^{
__strong typeof(wself) sself = wself;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(3);
[sself test];
})
};
3秒后的打印結(jié)果如下
2019-06-22 14:56:58.031208+0800 newtest[22005:3698610] invoke test
2019-06-22 14:56:58.031262+0800 newtest[22005:3698610] self dealloc
具體的調(diào)用流程與打印如下
NSLog(@"block調(diào)用之前 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
self.block();
NSLog(@"block調(diào)用之后 %ld",CFGetRetainCount((__bridge CFTypeRef)self));
2019-06-22 15:17:44.252439+0800 newtest[22012:3701260] block調(diào)用之前 7
2019-06-22 15:17:44.252520+0800 newtest[22012:3701260] block調(diào)用之后 8
異步調(diào)用的block回調(diào)會(huì)因?yàn)镚CD引用sself的值從而強(qiáng)引用了self祈惶,使得GCD的回調(diào)能夠正常執(zhí)行。