定时器强引用问题

2019-01-31  本文已影响0人  iOS_Yang

通常使用定时器有NSTimer 和 CADisplayLink,但是在使用过程中总会强引用当前对象,如下:

// scheduled开头创建的定时器默认已经添加到RunLoop中
self.timer =[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(testTimer) userInfo:nil repeats:YES];

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(testTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(testLink)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

避免设置target强引用解决方案
方案一
由于NSTimer可以通过block的方式直接设置定时任务可以避免设置target引起的强引用。

[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        // 定时任务
}];

方案二

创建一个代理TimerProxy,将代理作为定时器target。

image
TimerProxy.h

#import <Foundation/Foundation.h>

@interface HTTimerProxy : NSObject
+ (instancetype)timerProxyWithTaget:(id)target;
@property (nonatomic, weak) id target;
@end

TimerProxy.m

#import "HTTimerProxy.h"

@implementation HTTimerProxy

+ (instancetype)timerProxyWithTaget:(id)target
{
    HTTimerProxy *proxy = [[self alloc] init];
    proxy.target = target;
    return proxy;
}

// 利用runtime的消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}
@end

NSTimer与CADisplayLink将HTTimerProx作为代理

// scheduled开头创建的定时器默认已经添加到RunLoop中
self.timer =[NSTimer scheduledTimerWithTimeInterval:1.0 target:[HTTimerProxy timerProxyWithTaget:self] selector:@selector(testTimer) userInfo:nil repeats:YES];

CADisplayLink *link = [CADisplayLink displayLinkWithTarget:[HTTimerProxy timerProxyWithTaget:self] selector:@selector(testLink)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

方案三
创建一个集成NSObject的分类TimerWeakTarget,创建类方法---开启定时器的方法

#import <Foundation/Foundation.h>

@interface TimerWeakTarget : NSObject

@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;
@property (nonatomic, weak) id target;


/** 
 1.重写开启定时器方法,在内部对target进行替换,换成本类(TimerWeakTarget)的对象即可
 2.不会造成循环引用了,原控制器OneViewController属性有timer对timer强应用,timer内部对self强引用,但是self在此方法内部被替换成了本类的对象(TimerWeakTarget *),而本类的对象不会对OneViewController强引用,则不会造成循环引用,也就不会造成内存泄露
 */
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats;

@end
#import "TimerWeakTarget.h"

@implementation TimerWeakTarget

+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats{

    TimerWeakTarget * timer = [TimerWeakTarget new];
    timer.target = aTarget;
    timer.selector = aSelector;
    //-------------------------------------------------------------此处的target已经被换掉了不是原来的VIewController而是TimerWeakTarget类的对象timer
    timer.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:timer selector:@selector(fire:) userInfo:userInfo repeats:repeats];
    return timer.timer;
}

-(void)fire:(NSTimer *)timer{

    if (self.target) {
        [self.target performSelector:self.selector withObject:timer.userInfo];
    } else {

        [self.timer invalidate];
    }
}

@end

参考文章
https://www.cnblogs.com/CoderHong/p/9389753.html

https://www.cnblogs.com/adampei-bobo/p/5460988.html

上一篇下一篇

猜你喜欢

热点阅读