如果忘记消息转发,我们就先来复习一下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