打破NSTimer循环引用的方案

NSTimer相信大家经常都会用到,但是,如果不注意的话,很容易就会造成内存泄漏。
我们先来暂时一个例子:

@interface HomeControllerViewController ()

@property(nonatomic, strong)NSTimer *timer;

@end

@implementation HomeControllerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.view.backgroundColor = [UIColor redColor];
    
    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerSchedue:) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

- (void)timerSchedue:(NSTimer *)timer {
    
    static int i = 0;
    NSLog(@"%d",i);
    i++;
}

- (void)dealloc {
    
    NSLog(@"已经销毁HomeControllerViewController");
}

@end

如果你从A controller推进一个Homecontroller,然后再点返回,那么造成的现象就是,一直打印1,2,3......不会停止,直到你杀掉进程。并且,NSLog(@"已经销毁HomeControllerViewController");这句不会打印。

原因分析
为什么会这样呢?
可能有人会猜:既然不走delloc方法,那肯定就是有循环引用在,导致不能释放HomeControllerViewController,那么,我把timer的target传weakSelf,会不会解决这个问题呢?
经过试验之后,答案是,并不能,还是不走delloc方法。

那么,我们要怎么解决这个问题呢?
我们先来看看文档:


image.png

文档上有提到,The receiver retains a Timer。很明显,接收者会强引用timer,那么你穿weakSelf就没什么用了。

image.png

如果我们采取一个proxy类,把他的引用断开呢?


image.png

这样是否可以解决这个问题呢?按原理来说是应该可以的。我们来尝试下:
我们先创建一个weakProxy类:

#import 

NS_ASSUME_NONNULL_BEGIN

@interface WeakProxy : NSProxy

@property (weak,nonatomic,readonly)id target;

+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;

@end

NS_ASSUME_NONNULL_END


#import "WeakProxy.h"

@implementation WeakProxy

- (instancetype)initWithTarget:(id)target {
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(id)target {
    return [[WeakProxy alloc] initWithTarget:target];
}

- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [_target respondsToSelector:aSelector];
}

- (BOOL)isEqual:(id)object {
    return [_target isEqual:object];
}

- (NSUInteger)hash {
    return [_target hash];
}

- (Class)superclass {
    return [_target superclass];
}

- (Class)class {
    return [_target class];
}

- (BOOL)isKindOfClass:(Class)aClass {
    return [_target isKindOfClass:aClass];
}

- (BOOL)isMemberOfClass:(Class)aClass {
    return [_target isMemberOfClass:aClass];
}

- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
    return [_target conformsToProtocol:aProtocol];
}

- (BOOL)isProxy {
    return YES;
}

- (NSString *)description {
    return [_target description];
}

- (NSString *)debugDescription {
    return [_target debugDescription];
}

@end

这个时候,我们的timer的target要改一下,改成weakproxy:

self.timer = [NSTimer timerWithTimeInterval:1 target:[WeakProxy proxyWithTarget:self] selector:@selector(timerSchedue:) userInfo:nil repeats:YES];

我们运行来看看效果:


image.png

果然,delloc的方法执行了,说明,循环引用被打破了。

所以,后面如果用到NSTimer的时候,记得要注意这个问题哦.....

你可能感兴趣的:(打破NSTimer循环引用的方案)