面试题runtime基础

消息转发、NSProxy解决NSTimer的内存泄漏

2018-05-30  本文已影响113人  蔚尼

如果忘记消息转发,我们就先来复习一下Runtime笔记四:动态消息转发

开始正文:

一.NSProxy是什么:

二.解决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
  1. 然后,这样创建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打破循环引用

注意:NSProxy里面对controller是弱引用的。这样就可以打破循环,controller和timer就可以得到释放。


总结:
解决NSTimer内存泄漏的办法
1.使用NSProxy
2.重写如下的2个方法(消息转发里面的完整消息转发):

参考:NSproxy


写完之后,我回顾了一下消息转发。
调用方法即发送消息的时候,如果找不到要执行的方法,就会按以下顺序执行消息转发:
1.动态方法解析:让当前类里面的其他方法替代未找到的方法执行
2.备用接受者:让其他对象里面的方法来替代为找到的方法
3.完整消息转发:和2相同

我们前面用的是3.完整消息转发,那2.备用接受者应该也可以啊,让controller成为备用接受者,去controller里面寻找要执行的方法。

如下,把完整消息转发部分改为备用接收者的方法。成功实现弱引用!

//备用接收者
- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

所以,解决NSTimer内存泄漏的办法
1.使用NSProxy
2.重写如下的2个方法(消息转发里面的完整消息转发):

或者重写这个方法

上一篇 下一篇

猜你喜欢

热点阅读