如果忘記消息轉發,我們就先來復習一下Runtime筆記四:動態消息轉發。
開始正文:
一.NSProxy是什么:
- NSProxy是一個抽象的基類,是根類,與NSObject類似
- NSProxy和NSObject都實現了<NSObject>協議
- 提供了消息轉發的通用接口
二.解決NSTimer的內存泄漏(解決沒有被釋放的問題)
有一個SecondViewController用了NSTimer,SecondViewController里面按照以下寫法:
假如我PushSecondViewController,然后pop。 會發現Controller沒有被釋放,timer也沒有被取消。
#import "SecondViewController.h"
@interface SecondViewController ()
@property (strong,nonatomic)NSTimer * timer;
@end
@implementation SecondViewController
- (void)viewDidLoad{
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:1
target:self
selector:@selector(timerInvoked:)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)timerInvoked:(NSTimer *)timer{
NSLog(@"1");
}
- (void)dealloc{
NSLog(@"Dealloc");
}
@end
我們可以在dealloc中,調用Timer取消嗎?比如
- (void)dealloc{
[self.timer invalidate];
NSLog(@"Dealloc");
}
當然不行,因為Controller根本沒有被釋放,dealloc方法根本不會調用。
當然,破壞這種循環引用的方式有很多種。本文主要講解如何用NSProxy來破壞。
解決:
1.寫一個WeakProxy來實現弱引用
@interface WeakProxy : NSProxy
@property (weak,nonatomic,readonly)id target;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
@end
@implementation WeakProxy
- (instancetype)initWithTarget:(id)target{
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target{
return [[self alloc] initWithTarget:target];
}
//消息轉發
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [self.target methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
@end
- 然后,這樣創建Timer。讓NSProxy來執行timer的方法,但是NSProxy里面并沒有timer要調用的這個方法,就會執行消息轉發,消息轉發里面讓controller去執行timer的事件。這樣你會發現可以釋放了。
self.timer = [NSTimer timerWithTimeInterval:1 target:[WeakProxy proxyWithTarget:self] selector:@selector(timerInvoked:) userInfo:nil repeats:YES];
如下圖,我們是把Controller和NSTimer之間的互相引用打破了。新建一個NSProxy來實現這個弱引用。表面上讓NSProxy來實現timer的事件,但是通過消息轉發的方法,讓controller去完成了。
原本是這樣的:
使用NSProxy打破循環引用:
注意:NSProxy里面對controller是弱引用的。這樣就可以打破循環,controller和timer就可以得到釋放。
總結:
解決NSTimer內存泄漏的辦法
1.使用NSProxy
2.重寫如下的2個方法(消息轉發里面的完整消息轉發):
- methodSignatureForSelector:
- forwardInvocation:
參考:NSproxy
寫完之后,我回顧了一下消息轉發。
調用方法即發送消息的時候,如果找不到要執行的方法,就會按以下順序執行消息轉發:
1.動態方法解析:讓當前類里面的其他方法替代未找到的方法執行
2.備用接受者:讓其他對象里面的方法來替代為找到的方法
3.完整消息轉發:和2相同
我們前面用的是3.完整消息轉發,那2.備用接受者應該也可以啊,讓controller成為備用接受者,去controller里面尋找要執行的方法。
如下,把完整消息轉發部分改為備用接收者的方法。成功實現弱引用!
//備用接收者
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
所以,解決NSTimer內存泄漏的辦法
1.使用NSProxy
2.重寫如下的2個方法(消息轉發里面的完整消息轉發):
- methodSignatureForSelector:
- forwardInvocation:
或者重寫這個方法
- (id)forwardingTargetForSelector:(SEL)selector