NSTimer在使用过程中可能会引发内存泄露,如何利用NSProxy来处理这个问题?
场景
例如下述场景,我们希望在当前 viewController dealloc 后,自动销毁 NSTimer 和 CADisplayLink 的实例对象。
结论:无法执行 dealloc ,timer 和 displayLick 无法停止
- (void)viewDidLoad {
[super viewDidLoad];
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:YES];
}
- (void)test{
NSLog(@"%s",__func__);
}
- (void)dealloc{
NSLog(@"%s",__func__);
[self.timer invalidate];
[self.link invalidate];
}
- 分析
CADisplayLink、NSTimer 会对 target 产生强引用,如果 target 又对它们产生了强引用,那么就会引发循环引用
解决方案
1. 提前手动释放
通常我们会在 viewWillDisappear 方法里 调用 [self.timer invalidate] 提前释放 timer
2. 自定义代理类
虽然手动管理很方便,但是不优雅,因此我们想从 强引用 的 target 入手,只要实现了弱引用,就可以打破这层循环;实现方式很简单,创建一个 协议类 ,协议类 弱引用 self,再将其传给 timer 的 target 即可;
@interface DNProxy : NSObject
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation DNProxy
+ (instancetype)proxyWithTarget:(id)target{
DNProxy *proxy = [[DNProxy alloc] init];
proxy.target = target; // 弱引用传进来的target
return proxy;
}
// 由于NDProxy没有"self"对象的的实例方法,因此需要消息转发实现 self 的方法调用
- (id)forwardingTargetForSelector:(SEL)aSelector{
// 此处return _target后,会立刻进行 objc_msgSend(_target, aSelector);
return _target;
}
@end
调用方式也很简单:
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[DNProxy proxyWithTarget:self] selector:@selector(test) userInfo:nil repeats:YES];
3. NSProxy
和 方式2 很像,不过是系统自带的 NSProxy 类,专门用来处理类似的情况,虽然看起来比 方式2 要麻烦一点,但是实际上,我们略过了去父类中进行 方法查找 这一过程,不需要 动态方法解析,直接进行 方法签名 以及 方法调用
@interface MyProxy : NSProxy
@property (nonatomic, weak) id target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation MyProxy
+ (instancetype)proxyWithTarget:(id)target{
// NSProxy对象不需要调用init,因为它本来就没有init方法
MyProxy *proxy = [MyProxy alloc];
proxy.target = target; // 弱引用传进来的target
return proxy;
}
// 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [_target methodSignatureForSelector:sel];
}
// 调用
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:_target];
}
@end
阅读量
loading...