博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
十一、关于NSTimer造成的内存泄漏和解决方法
阅读量:6246 次
发布时间:2019-06-22

本文共 4823 字,大约阅读时间需要 16 分钟。

NSTimer 定时器 eg:

- (void)viewDidLoad {    [super viewDidLoad];    self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];    [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];}- (void)timerAction:(NSTimer *)timer{    NSLog(@"%@", timer);}复制代码

       但是,如果你将timer所属的控制器推出后,发现timer此时还在执行,这是因为timer的执行需要依赖于runloop,在timer创建好放入runloop之后,并且如果timer是循环执行的,如果不显示调用invalidate方法,那么timer是停不下来的。        同时,如果此时你重写了这个控制器的dealloc方法,并且让这个控制器pop出navigation所管理的栈时(我的这个控制器是由navigation所push出来的),你会发现dealloc方法并不会执行,这表明控制器并没有被释放,这是为什么呢,这是因为NSTimer在添加target时,会对这个target进行retain。所以就会造成上面这种情况:控制器要释放,就要释放它的所有实例变量,当释放到timer时,timer要释放他所持有的target,而此时的target是该控制器,所以造成了循环引用,从而造成了内存泄露。        要避免这种情况,我能想到的一个办法就是在设定timer的target时,将target-action保存,target改设置为另一个和timer不存在引用关系的变量,进而避免泄露。 代码如下: 首先定义一个用来保存target-action的对象

@interface PltTimerTarget : NSObject@property (nonatomic, weak) id target;@property (nonatomic, assign) SEL selector;@property (nonatomic, weak) NSTimer *timer;@end@implementation PltTimerTarget- (void)pltTimerTargetAction:(NSTimer *)timer{    if (self.target) {        //该方法会在RunLoop为DefaultMode时才会调用,与timer的CommonMode冲突        //[self.target performSelector:self.selector withObject:timer afterDelay:0.0];        //该方法可以正常在CommonMode中调用,但是会报警告        //[self.target performSelector:self.selector withObject:timer];        //最终方法        IMP imp = [self.target methodForSelector:self.selector];        void (*func)(id, SEL, NSTimer*) = (void *)imp;        func(self.target, self.selector, timer);    } else {        [self.timer invalidate];        self.timer = nil;    }}@end复制代码

为 NSTimer 添加分类方法

+ (instancetype)pltScheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo{    PltTimerTarget *timerTarget = [[PltTimerTarget alloc] init];    timerTarget.target = aTarget;    timerTarget.selector = aSelector;    NSTimer *timer = [NSTimer timerWithTimeInterval:ti target:timerTarget selector:@selector(pltTimerTargetAction:) userInfo:userInfo repeats:YES];    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];    timerTarget.timer = timer;    return timerTarget.timer;}复制代码

然后就可以安全的使用NSTimer

- (void)viewDidLoad {    [super viewDidLoad];    self.timer = [NSTimer pltScheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerAction:) userInfo:@"userInfo"];    //这样创建的timer,target的dealloc方法不会执行,因为timer会持有target,进而造成循环引用//    self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerAction:) userInfo:@"userInfo" repeats:YES];}- (void)timerAction:(NSTimer *)timer{    NSLog(@"%@", timer.userInfo);}- (void)dealloc{    [self.timer invalidate];    self.timer = nil;    NSLog(@"%@ dealloc", self);}复制代码
第二种方式:利用NSProxy解决NSTimer内存泄漏问题

问题描述:        用NSTimer来实现每隔一定时间执行制定的任务,例如最常见的广告轮播图。如果我们在 timerWithTimeInterval:1 target:self 中指定target为当前控制器,控制器则会被timer强引用,而控制器对timer也是强引用的。一般,我们终止定时器往往在界面销毁时,即dealloc方法中写 [_timer invalidate];。基于上面的分析,由于循环引用的存在,控制器永远也不会走dealloc方法,定时器会一直执行方法,造成内存泄露。

解决方案:        利用消息转发来断开NSTimer对象与视图之间的引用关系。初始化NSTimer时把触发事件的target替换成一个单独的对象,然后这个对象中NSTimer的SEL方法触发时让这个方法在当前的视图self中实现。

背景知识:        NSProxy:NSProxy 是一个抽象类,它接收到任何自己没有定义的方法他都会产生一个异常,所以一个实际的子类必须提供一个初始化方法或者创建方法,并且重载forwardInvocation:方法和methodSignatureForSelector:方法来处理自己没有实现的消息。 从类名来看是代理类,专门负责代理对象转发消息的。相比NSObject类来说NSProxy更轻量级,通过NSProxy可以帮助Objective-C间接的实现多重继承的功能。 eg:

#import 
@interface XZHProxy : NSProxy/** * 代理的对象 */@property (nonatomic,weak)id obj;@end复制代码
#import "XZHProxy.h"@implementation XZHProxy/** 这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行    为给定消息提供参数类型信息 */- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{    NSMethodSignature *sig = nil;    sig = [self.obj methodSignatureForSelector:aSelector];    return sig;}/** *  NSInvocation封装了NSMethodSignature,通过invokeWithTarget方法将消息转发给其他对象.这里转发给控制器执行。 */- (void)forwardInvocation:(NSInvocation *)anInvocation{    [anInvocation invokeWithTarget:self.obj];}@end复制代码
#import "ViewController.h"#import "XZHProxy.h"@interface ViewController ()/** *代理 */@property (nonatomic,strong)XZHProxy *proxy;/** *  定时器 */@property (nonatomic,strong)NSTimer *timer;/** *  计数 */@property (nonatomic,assign)NSInteger count;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    self.proxy = [XZHProxy alloc];    //作为当前控制器的代理    self.proxy.obj = self;    //target为代理    self.timer = [NSTimer timerWithTimeInterval:1 target:self.proxy selector:@selector(timerEvent) userInfo:nil repeats:YES];    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];}- (void)timerEvent{    NSLog(@"%zd",self.count++);}- (void)dealloc{    NSLog(@"----dealloc");    //释放计时器    [self.timer invalidate];}复制代码

转载地址:http://ydlia.baihongyu.com/

你可能感兴趣的文章
关于java@Override错误
查看>>
scrollTop和scrollLeft的兼容解决万全方法
查看>>
TreeSet
查看>>
经过几天的推敲学习
查看>>
Python Day30
查看>>
WebRequest对DNS说:没有你我依然可以
查看>>
jvm垃圾收集小记
查看>>
MonthCalendar的mousedown方法选择日期
查看>>
用于pytorch的H5Dataset接口(类比TensorDataset接口)
查看>>
Python-入门第三篇
查看>>
解决Cannot change version of project facet Dynamic Web M
查看>>
mysql备份与恢复
查看>>
hadoop实例sort
查看>>
jstat (JVM统计监测工具)
查看>>
git 免密码push,pull
查看>>
js懒加载
查看>>
计算某时间是年中第几周。
查看>>
【论文阅读】A mixed-scale dense convolutional neural network for image analysis
查看>>
用正则表达式匹配网址URL中最后一个反斜杠/后面的内容
查看>>
Define custom @Required-style annotation in Spring
查看>>