OC

内存管理-定时器

Posted on 2022-01-04,3 min read

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


下一篇: ipa砸壳→

loading...