一.ARC下用塊(block)的循環(huán)引用問題樣例探究:
當(dāng)頁面跳轉(zhuǎn)時,看界面是否有循環(huán)引用,如果控制器不走dealloc方法,說明有循環(huán)引用。
情況一:
-?(void)case1?{
????NSLog(@"case?1?Click");
????dispatch_after(dispatch_time(DISPATCH_TIME_NOW,?(int64_t)(0.3?*?NSEC_PER_SEC)),?dispatch_get_main_queue(),?^{
????????self.name?=?@"case?1";
????});
}
情況一:執(zhí)行了dealloc,不泄露,此情況雖然是block,但未形成保留環(huán)block -> self
情況二:
-?(void)case2
?{
?? ?__weak?typeof(self)?weakSelf?=?self;
????[self.teacher?requestData:^(NSData?*data)?{
????????typeof(weakSelf)?strongSelf?=?weakSelf;
???????strongSelf.name?=?@"case?2";
????}];
}
情況二:執(zhí)行了dealloc,不泄露,此情況就是內(nèi)存泄漏后的一般處理了 self ->teacher ->block ->strongSelf,后面那個strongSelf和原來的self并沒有直接關(guān)系,因為strongSelf是通過weakSelf得來的,而weakSelf又沒有強引用原來的self。
情況三:
-?(void)case3?{
????NSLog(@"case?3?Click");
????[self.teacher?requestData:^(NSData?*data)?{
????????self.name?=?@"case?3";
????}];
}
情況三:未執(zhí)行dealloc,內(nèi)存泄漏,此情況就是最典型的循環(huán)引用了,形成保留環(huán)無法釋放,self ->teacher ->block ->self
情況四:
-?(void)case4?{
????NSLog(@"case?4?Click");
????[self.teacher?requestData:^(NSData?*data)?{
????????self.name?=?@"case?4";
????????self.teacher?=?nil;
????}];
}
情況四:執(zhí)行了dealloc,不泄露,雖然也是保留環(huán),但通過最后一句,使self不再強引用teacher,打破了保留環(huán)
情況五:
-?(void)case5?{
????NSLog(@"case?5?Click");
????Teacher?*t?=?[[Teacher?alloc]?init];
????[t?requestData:^(NSData?*data)?{
????????self.name?=?@"case?5";
????}];
}
情況五:執(zhí)行了dealloc,不泄露,未形成保留環(huán) t ->block ->self
情況六:
-?(void)case6?{
????NSLog(@"case?6?Click");
????[self.teacher?callCase6BlackEvent];
????self.teacher.case6Block?=?^(NSData?*data)?{
????????self.name?=?@"case?6";
????????//下面兩句代碼任選其一
????????self.teacher?=?nil;
//????????self.teacher.case6Block?=?nil;
????};
}
情況六:執(zhí)行了dealloc,不泄露,最后兩句代碼任選其一即可防止內(nèi)存泄漏,self.teacher 或者 case6Block 置為空都可以打破 retain cycle。
PS: 雖然情況四、情況六的寫法都可以防止內(nèi)存泄漏,不過為了統(tǒng)一,個人建議最好還是按照普通寫法即情況二的寫法。
二.__block和__weak的區(qū)別:
? ?1.__block不管是在ARC還是在MRC模式下,都可以使用,還可以修飾基本數(shù)據(jù)類型
? 2.__weak只能使用在ARC模式下,并且只能修飾對象(NSString),不可以修飾基本數(shù)據(jù)類型
?3.__weak不可以改變變量值,__block可以改變變量值。
?4.__block對象在ARC下可能會導(dǎo)致循環(huán)引用,非ARC下會避免循環(huán)引用,__weak只在ARC下使用,可以避免循環(huán)引用。
三.__block修飾變量在block內(nèi)部改變其變量值的原理
Block不允許修改外部變量的值,這里所說的外部變量的值,指的是棧中指針的內(nèi)存地址。__block?所起到的作用就是只要觀察到該變量被 block 所持有,就將“外部變量”在棧中的內(nèi)存地址放到了堆中。進而在block內(nèi)部也可以修改外部變量的值。
eg:
__blockinta =0;
NSLog(@"定義前:%p", &a);//棧區(qū)
void(^foo)(void) = ^{
?a =1;
NSLog(@"block內(nèi)部:%p", &a);//堆區(qū)
};
NSLog(@"定義后:%p", &a);//堆區(qū)
foo();
2016-05-1702:03:33.559LeanCloudChatKit-iOS[1505:713679]定義前:0x16fda86f8
2016-05-1702:03:33.559LeanCloudChatKit-iOS[1505:713679]定義后:0x155b22fc8
2016-05-1702:03:33.559LeanCloudChatKit-iOS[1505:713679]block內(nèi)部: 0x155b22fc8
“定義后”和“block內(nèi)部”兩者的內(nèi)存地址是一樣的,我們都知道 block 內(nèi)部的變量會被 copy 到堆區(qū),“block內(nèi)部”打印的是堆地址,因而也就可以知道,“定義后”打印的也是堆的地址。
那么如何證明“block內(nèi)部”打印的是堆地址?
把三個16進制的內(nèi)存地址轉(zhuǎn)成10進制就是:
定義后前:6171559672
block內(nèi)部:5732708296
定義后后:5732708296
中間相差438851376個字節(jié),也就是 418.5M 的空間,因為堆地址要小于棧地址,又因為iOS中一個進程的棧區(qū)內(nèi)存只有1M,Mac也只有8M,顯然a已經(jīng)是在堆區(qū)了。
這也證實了:a 在定義前是棧區(qū),但只要進入了 block 區(qū)域,就變成了堆區(qū)。這才是__block關(guān)鍵字的真正作用。
理解到這是因為堆棧地址的變更,而非所謂的“寫操作生效”,這一點至關(guān)重要,要不然你如何解釋下面這個現(xiàn)象: