美文网首页
NSTimer引起的内存泄漏

NSTimer引起的内存泄漏

作者: 皮蛋豆腐酱油 | 来源:发表于2019-06-15 14:19 被阅读0次

    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 <objc/message.h>
    @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 <Foundation/Foundation.h>
    
    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引起的内存泄漏

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