美文网首页我爱编程
iOS安全防护之三:定时器内存泄漏

iOS安全防护之三:定时器内存泄漏

作者: 进击的iOS开发 | 来源:发表于2018-08-07 13:25 被阅读2次

前言:在使用定时器地过程中,如果没有在合适的位置销毁定时器往往会导致定时器无法释放而发生内存泄漏,定时器也会持续消耗CPU资源,电量。所以需要一种能自动释放地定时器,方便开发使用。


消除内存泄漏主要有两种方法,一种是断环避免循环引用,一种是主动断开避免循环引用。这里的NSTimer将会用两种方法来解决循环引用地问题

首先分析一下NSTimer为啥会带来循环引用

1. NSTimer地支撑是Runloop。Runloop会强引用NSTimer,所以必须手动调用NSTimer的失效方法。
2. NSTimer会强引用所服务的对象,一般对象(VC)也会强引用定时器(弱引用同样不能释放)。

1.第一种解决方案:断环避免循环引用

知识贮备:1.weak属性不会造成强引用。2.iOS有两个基类,一个是NSObject,领一个就是今天用到的NSProxy,一个专门用来做消息转发的对象,因为他地效率更高(用NSObject也完成没有问题)

原理:添加一个中间对象,这个中间对象强引用被定时器强引用,而定时器被VC强应用,中间对象弱应用VC。这样VC就不会被循环引用,我们在VC地析构函数里面失效定时器。


强引用是实线,弱引用是虚线

需要新建一个中间对象,继承自NSProxy
.h文件

#import <Foundation/Foundation.h>

@interface YYEProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

.m文件,只做了一件是,就是消息转发

#import "YYEProxy.h"

@implementation YYEProxy

+ (instancetype)proxyWithTarget:(id)target
{
    // NSProxy对象不需要调用init,因为它本来就没有init方法
    YYEProxy *proxy = [YYEProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}
@end

使用方法,注意在dealloc里面失效定时器

#import "YYEProxy.h"

@interface SecViewController ()

@property (strong, nonatomic) NSTimer *timer;

@end

@implementation SecViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[YYEProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}

- (void)timerTest {
    NSLog(@"%s", __func__);
}

- (void)dealloc {
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}

@end

2.第二种解决方案:主动断开避免循环引用

知识贮备:1.weak属性在其指向对象销毁地时候的时候回自动置为nil。

原理:和第一中方法类似,这里也要引入中间对象,由中间对象弱引用对象(VC)。不同的是,VC可以不持有(也可以持有,看需求)。定时器强引用中间对象,中间对象弱(强也行)引用定时器,这样在对象被销毁地时候,weak指针为nil。在定时函数里面去自动地销毁定时器。


image

用分类实现 .h文件

#import <Foundation/Foundation.h>

@interface NSTimer (WeakTimer)

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

@end

.m文件

#import "NSTimer+WeakTimer.h"

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

- (void)fire:(NSTimer *)timer;
@end

@implementation TimerWeakObject

- (void)fire:(NSTimer *)timer
{
    if (self.target) {
        if ([self.target respondsToSelector:self.selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [self.target performSelector:self.selector withObject:timer.userInfo];
#pragma clang diagnostic pop
        }
    }
    else{
        [self.timer invalidate];
    }
}

@end

@implementation NSTimer (WeakTimer)

+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
                                         target:(id)aTarget
                                       selector:(SEL)aSelector
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats
{
    TimerWeakObject *object = [[TimerWeakObject alloc] init];
    object.target = aTarget;
    object.selector = aSelector;
    object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats];
    
    return object.timer;
}

@end

使用方法

#import "NSTimer+WeakTimer.h"

@interface SecViewController ()

@property (strong, nonatomic) NSTimer *timer;

@end

@implementation SecViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
   self.timer = [NSTimer scheduledWeakTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
    
}
// 如果不点击屏幕,定时器也会随着控制器地销毁而销毁,具体使用看需求
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    [self.timer invalidate];
    self.timer = nil;
}

- (void)timerTest {
    NSLog(@"%s", __func__);
}

@end

最间用法

#import "NSTimer+WeakTimer.h"

@implementation SecViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [NSTimer scheduledWeakTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}

- (void)timerTest {
    NSLog(@"%s", __func__);
}

@end

总结:第一种方法使用较为第二种方法麻烦,我推荐使用第二种方法,只要定时器随着对象地生命周期,就可以做到自动销毁。如果您集成之后发现有什么问题,欢迎与我一起探讨!

相关文章

  • iOS安全防护之三:定时器内存泄漏

    前言:在使用定时器地过程中,如果没有在合适的位置销毁定时器往往会导致定时器无法释放而发生内存泄漏,定时器也会持续消...

  • iOS 内存泄漏三两事

    iOS 内存泄漏三两事 iOS 内存泄漏三两事

  • GCD定时器封装

    前言 iOS开发中,经常需要使用到定时器,使用NSTimer很容易出现内存泄漏,在此简单封装GCD定时器。 1.定...

  • iOS内存管理

    摘自《iOS程序员面试笔试宝典》 一、内存管理 1.什么是内存泄漏?什么是安全释放 内存泄漏指动态分配内存的对象在...

  • iOS笔记-记录一次内存泄漏发现过程

    iOS笔记-记录一次内存泄漏发现过程 iOS笔记-记录一次内存泄漏发现过程

  • iOS内存泄漏有哪些

    iOS内存溢出和内存泄漏的情况

  • 一步步调试解决iOS内存泄漏

    一步步调试解决iOS内存泄漏 一步步调试解决iOS内存泄漏

  • iOS性能优化-内存泄漏

    前言:本文旨在介绍iOS性能优化中有关内存泄漏的介绍和检测。 一、什么是内存泄漏? 内存泄漏是指申请的内存空间使用...

  • iOS 内存泄漏排查方法及原因分析

    本文将从以下两个层面解决iOS内存泄漏问题: 内存泄漏排查方法(工具) 内存泄漏原因分析(解决方案) 在正式开始前...

  • NSTimer学习笔记

    NSTimer是iOS最常用的定时器工具之一,在使用的时候常常会遇到各种各样的问题,最常见的是内存泄漏,通常我们使...

网友评论

    本文标题:iOS安全防护之三:定时器内存泄漏

    本文链接:https://www.haomeiwen.com/subject/wggovftx.html