NSTimer引起的内存泄漏

1.这个是不能解决内存泄漏的问题的,当前传进去的是weakself,但是在NSTimer内部,又对self进行了一次强引用

@property (nonatomic, strong) NSTimer *timer;

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak typeof(self) weakself = self;
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:weakself selector:@selector(log) userInfo:nil repeats:YES];
    
}

- (void)log {
    NSLog(@"123");
}

2.方法一:借助runtime

#import "ViewController.h"
#import 
@interface ViewController()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) id target;
@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    _target = [NSObject new];
    class_addMethod([_target class], @selector(log), class_getMethodImplementation([self class], @selector(log)), "v@:");
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:_target selector:@selector(log) userInfo:nil repeats:YES];
    //这样NSTimer就强引用了target,问题就解决了
}

- (void)log {
    NSLog(@"123");
}

- (void)dealloc {
    NSLog(@"%@ dealloc",self);
    [self.timer invalidate];
    self.timer = nil;
    //这样我们就可以在当前viewController的dealloc函数当中释放timer,从而达到内存及时回收的效果
}
@end

3.方法二:借助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间接的实现多重继承的功能。

//
//  MyProxy.h

#import 

NS_ASSUME_NONNULL_BEGIN

@interface MyProxy : NSProxy
@property (nonatomic, weak) id target;
@end

NS_ASSUME_NONNULL_END
//
//  MyProxy.m

#import "MyProxy.h"

@implementation MyProxy
/**
 *  NSInvocation封装了NSMethodSignature,通过invokeWithTarget方法将消息转发给其他对象.这里转发给控制器执行。
 */
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}
/**
 这个函数让重载方有机会抛出一个函数的签名,再由forwardInvocation:去执行
 为给定消息提供参数类型信息
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}
@end

//
//  ViewController.m

#import "ViewController.h"
#import "MyProxy.h"
@interface ViewController()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong)MyProxy *myProxy;
@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    _myProxy = [MyProxy alloc];
    _myProxy.target = self;
    //会在当前runloop当中注册一个Timer,默认是defaultMode当中
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:_myProxy selector:@selector(log) userInfo:nil repeats:YES];
    
}

- (void)log {
    NSLog(@"123");
}

- (void)dealloc {
    NSLog(@"%@ dealloc",self);
    [self.timer invalidate];
    self.timer = nil;
    //这样我们就可以在当前viewController的dealloc函数当中释放timer,从而达到内存及时回收的效果
}
@end

链接:https://www.jianshu.com/p/1ef002fcf314

你可能感兴趣的:(NSTimer引起的内存泄漏)