雖然目前IOS都普遍使用了ARC開發(fā)荡短,但是還是有一些情況下是必然存在循環(huán)引用的来吩,為了更好的說明循環(huán)引用。先以MRC的代碼來描述一些內(nèi)存關(guān)系了讨。
@interface ViewController ()
@end
@implementation ViewController
(void)open{
//av 引用計數(shù) 1
AViewController *av = [[AViewController alloc] init];
// presentViewController的時候 AViewController引用計數(shù) 2
[self presentViewController:av animated:YES completion:^{
}];
// 這里釋放一次 AViewController 引用計數(shù) 1
[av release];
}(void)addButton{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor redColor];
button.frame = CGRectMake(100, 100, 100, 100);
[button addTarget:self action:@selector(open) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}(void)viewDidLoad {
[super viewDidLoad];
[self addButton];
}
@end
這里我們看到 AViewController *av 的引用計數(shù)是1, 以下是 AViewController 的代碼
@implementation AViewController
(void)back{
// AViewController 引用計數(shù)-1 (正常情況的話會形成 0)
[self dismissViewControllerAnimated:YES completion:^{
}];
}(void)viewDidLoad {
[super viewDidLoad];
}
@end
正常邏輯下 當(dāng)我們 調(diào)用 back 的時候 這個時候 AViewController 會因?yàn)?dismissViewControllerAnimated 使得引用計數(shù)變成0内颗,然后完成正常的內(nèi)存釋放钧排。
這個時候我們?nèi)胍粋€ BClass 先看 BClass的定義
@protocol BClassDelegate <NSObject>
- (void)finished;
@end
@interface BClass : NSObject
@property (nonatomic, retain) id delegate; - (void)startAnimation;
@end
注意 delegate 是 retain 關(guān)鍵點(diǎn)來了。這種設(shè)計擺明了跟其他 委托模式不一樣(參考UITableView 的 delegate 是assign[因?yàn)槟壳笆荕RC 所以不是weak])均澳。
這個時候 在 AViewController 使用 BClass卖氨。
@interface AViewController ()
@property (nonatomic, retain) BClass *animation;
@end
(void)userBClass{
//tempAnimation 引用計數(shù) 1
_animation = [[BClass alloc] init];
//關(guān)鍵點(diǎn)(這里 AViewController 引用計數(shù)變成了 2)
_animation.delegate = self;
}(void)viewDidLoad {
[super viewDidLoad];
[self userBClass];
}
為了降低復(fù)雜度 我們先只考慮AViewController 的內(nèi)存釋放,先暫時放下 animation的內(nèi)存釋放問題负懦。
這里重點(diǎn)情況是 _animation.delegate = self; 這里會使得AViewController 引用計數(shù)變成2筒捺。
看下 BClass 源碼
@implementation BClass
- (void)dealloc{
NSLog(@"%s", func);
[_delegate release];
_delegate = nil;
[super dealloc];
} - (void)startAnimation{
}
// 這里傳進(jìn)來的 adelegate 是 AViewController - (void)setDelegate:(id)adelegate{
[_delegate release];
[adelegate retain]; //關(guān)鍵的一部使得這個引用計數(shù) + 1 AViewController(引用計數(shù)變成2)
_delegate = adelegate;
}
@end
關(guān)鍵點(diǎn)是因?yàn)?delegate 設(shè)計成 retain類型,所以 _animation.delegate = self; 這句代碼使得 AViewController 變成了2,
這個時候 dismissViewControllerAnimated 引用計數(shù)減1, 但是 AViewController 引用計數(shù)沒有變成0纸厉,所以AViewController內(nèi)存沒有釋放系吭,這就造成內(nèi)存泄漏。到這里給出完整BClass的代碼
@interface AViewController ()
@property (nonatomic, retain) BClass *animation;
@end
@implementation AViewController
(void)dealloc
{
[_animation release];
_animation = nil;
[super dealloc];
}(void)back{
// AViewController 引用計數(shù)-1 (正常情況的話會形成 0)
[self dismissViewControllerAnimated:YES completion:^{
}];
}(void)userBClass{
//tempAnimation 引用計數(shù) 1
_animation = [[BClass alloc] init];
//關(guān)鍵點(diǎn)(這里 AViewController 引用計數(shù)變成了 2)
_animation.delegate = self;
}
這里因?yàn)?back的時候 我們沒有辦法使得AViewController引用計數(shù)變成0颗品,所以沒有調(diào)用AViewController的dealloc肯尺,所以無法
觸發(fā) [_animation release]; 進(jìn)而使得 BClass也沒有被釋放內(nèi)存。這里就是循環(huán)引用了躯枢。
解決方案可以這樣考慮则吟,只要在調(diào)用back之前我們能夠使得 AViewController的引用計數(shù)由2變成1就可以了。
這個時候改寫back
- (void)back{
// 先使得AViewController 引用計數(shù)-1
[_animation.delegate release];
// AViewController 引用計數(shù)-1 (正常情況的話會形成 0)
[self dismissViewControllerAnimated:YES completion:^{
}];
}
這樣就可以在調(diào)用back的時候 我們的AViewController引用計數(shù)減了2次,這種寫法非常丑陋锄蹂,雖然能夠解決問題氓仲,但是不夠方便。
參考網(wǎng)絡(luò)上有一種通過Proxy來處理這種循環(huán)引用的 先看下TestProxy的定義
@interface TestProxy : NSProxy
@property (nonatomic, assign) id target;
@end
@implementation TestProxy
(void)dealloc{
[super dealloc];
}(NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature *signature = [_target methodSignatureForSelector:selector];
return signature;
}(void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = invocation.selector;
if (_target) {
if ([_target respondsToSelector:sel]) {
[invocation invokeWithTarget:_target];
}
}
}
@end
這里可以先忽略 methodSignatureForSelector 和 forwardInvocation 先重點(diǎn)放在內(nèi)存泄漏上得糜。
引入TestProxy后敬扛,改寫AViewController里面的userBClass和dealloc,back
(void)dealloc
{
[_animation release];
[_proxy release];
[super dealloc];
}(void)back{
[self dismissViewControllerAnimated:YES completion:^{
}];
}(void)userBClass{
//_animation 引用計數(shù)1
_animation = [[BClass alloc] init];
// _proxy 引用計數(shù)1
_proxy = [TestProxy alloc];
// _proxy 引用計數(shù)2
_animation.delegate = _proxy;
}
重點(diǎn)是 _animation.delegate = _proxy 這里并沒有引起AViewController 引用計數(shù)的改變朝抖,所以 back的時候 AViewController能正常釋放內(nèi)存啥箭。所以 back觸發(fā)的時候 會進(jìn)入 dealloc 這個時候 [_animation release]; 會使得_animation引用計數(shù)變成了0。
到這里我們能夠正常釋放了 AViewController和BClass了治宣。剩下只有能解決NSProxy正常釋放那么就能夠解決所有的內(nèi)存泄漏問題了急侥。 目前 proxy引用計數(shù)是2砌滞,參考代碼可以知道 AViewController 的 dealloc 有一次 [_proxy release], BClass里面有一個
[_delegate release], BClass里面的delegate就是_proxy 所以 TestProxy的引用計數(shù)變成0,最終 AViewController, BClass 和 TestProxy 出現(xiàn)的3個類都成功完成內(nèi)存釋放坏怪。
對比原來非常粗暴的back函數(shù)里面調(diào)用release來解決內(nèi)存問題贝润,另一種通過引入 TestProxy 把釋放內(nèi)存的時機(jī)放到了 AViewController 的 dealloc。一般情況建議大家這樣引用TestProxy, 因?yàn)檫@樣寫法不需要考慮內(nèi)存釋放的時機(jī)的陕悬。同理NSTimer這種也可以引入Proxy,然后在 dealloc 調(diào)用 invalidate即可按傅。
總結(jié):循環(huán)引用計數(shù)的關(guān)鍵點(diǎn)在于 setDelegate方法捉超, aaa.delegate = self; aaa.delegate = nil 。