NSTimer产生的问题及解决方案

  计时器可以指定绝对的日期和时间,以便到时执行任务也可以指定执行的任务的相对延迟时间,还可以重复运行任务。计时器要和runloop相关联,运行循环到时候会触发任务。虾米昂这个方法可以创建并预先安排到当前运行循环中:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

  target与selector参数表示计时器将在哪个对象上调用哪个方法。可以指定时间执行任务,也可以令其反复执行任务,直到自己手动将其关闭。计时器会保留其目标对象,等其失效时再释放此对象。若是重复模式需自己调用invalidate才能令其停止。

  由于计时器会保留其目标对象,所以反复执行任务会导致“保留环”(即循环引用)。问题代码如下:

#import "testViewController.h"

@interface testViewController (){
    NSTimer *_time;
}
- (void)startPolling;
- (void)stopPolling;

@end

@implementation testViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
    btn.backgroundColor = [UIColor redColor];
    [btn addTarget:self action:@selector(clickBtn) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];

    [self startPolling];
}

-(void)dealloc{
    NSLog(@"testVC dealloc");
    [_time invalidate];
}

-(void)startPolling{
    _time = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(doPoll) userInfo:nil repeats:YES];
}

-(void)stopPolling{
    [_time invalidate];
    _time = nil;
}

- (void)doPoll{
    NSLog(@"do something");
}

- (void)clickBtn{
    [self dismissViewControllerAnimated:YES completion:nil];
}

@end

  计时器的目标对象是self,所以保留此实例。但计时器是实例变量存放的,所以实例也保留了计时器。于是就产生了“保留环”。若想在系统回收奔雷实例的时候令计时器无效,从而打破保留环,那会陷入死结,因为_time对象有效时,该实例对象保留计数不会降为0,因为也不会调用dealloc方法,从而也无法调用invaildate方法,所以计时器一直处于有效果状态。从而导致内存泄露。若想释放必须主动调用stopPolling方法,坦诺作为公开API给别人使用,无法保证他人一定会调用此方法。

  解决方式:用block可解决,可为计时器添加以下功能:

#import <Foundation/Foundation.h>

@interface NSTimer (JBlocksSupport)

+ (NSTimer *)j_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;

@end

@implementation NSTimer (JBlocksSupport)

+ (NSTimer *)j_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats{
    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(j_blockInvoke:) userInfo:[block copy] repeats:repeats];

}

+ (void)j_blockInvoke:(NSTimer *)timer{
    void (^block) () = timer.userInfo;
    if (block) {
        block();
    }
}

@end

  解释:将计时器执行的任务封装成block,调用该方法时将block作为useInfo参数传进去。只要计时器有效,就会一直保留block,传入时需拷贝到“堆”上,否则可能会失效。target是类对象,此处也有保留环,但类对象无需回收,因此无需担心。使用方法如下:

-(void)startPolling{
    __weak testViewController *weakSelf = self;
    _time = [NSTimer j_scheduledTimerWithTimeInterval:1 block:^{
        [weakSelf doPoll];
    } repeats:YES];
}

  先定义弱引用,令其指向self,块捕获该弱引用,即self不会被计时器所保留。这样,在外界指向该类的实例的最后一个引用将其释放,则该实例便可被系统回收,会调用dealloc方法,还会调用invalidate方法,使计时器失效。

  记住:若将计时器设置为重复,则需调用invalidate将其失效,若一次性的话,在其触发完任务之后也会失效。

 

时间: 2024-10-07 06:59:09

NSTimer产生的问题及解决方案的相关文章

[crash详解与防护] NSTimer crash

前言: NSTimer会保留其目标对象,如果不加以注意,就会持有保留环,造成内存泄露. 一. NSTimer保留环介绍 Foundation框架中的NSTimer类,提供了在某个时间执行指定方法的功能,原型如下: + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeat

NSTimer 进入后台后持续进行解决方案

1.在Info.plist中,添加Required background modes键,value为:App plays audio 2.在程序启动方法(- (BOOL)application: didFinishLaunchingWithOptions:)中代码声明 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self

初识iOS NSTimer 循环引用不释放问题

原文转自 :http://www.codecate.com/code/?p=77 最近开发遇到NSTimer Target 造成循环引用问题,不释放,以下是解决方案. stackoverflow上的一个解决方案 http://stackoverflow.com/questions/16821736/weak-reference-to-nstimer-target-to-prevent-retain-cycle 原文如下 Weak Reference to NSTimer Target To Pr

容易导致循环引用的场景的解决方案

一.Block block的内部引用了对象的属性或者方法,导致block保留了对象,同时对象又保留了block,形成循环引用. 解决方案是,在ARC中采用__weak对对象进行弱化,在非ARC中采用__block对对象进行弱化.如下: @property(nonatomic, readwrite, copy) completionBlock completionBlock; //======================================== __weak typeof(self

iOS中常见 Crash 及解决方案

一.访问了一个已经被释放的对象 在不使用 ARC 的时候,内存要自己管理,这时重复或过早释放都有可能导致 Crash. 例子 NSObject * aObj = [[NSObject alloc] init]; [aObj release]; NSLog(@"%@", aObj); 原因 aObj 这个对象已经被释放,但是指针没有置空,这时访问这个指针指向的内存就会 Crash. 解决办法 使用前要判断非空,释放后要置空.正确的释放应该是: [aObj release]; aObj =

第四十三篇、利用NSProxy解决NSTimer内存泄漏问题

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

iOS开发中遇到的一些问题及解决方案【转载】

iOS开发中遇到的一些问题及解决方案[转载] 2015-12-29 [385][scrollView不接受点击事件,是因为事件传递失败] // //  MyScrollView.m //  Created by beyond on 15/6/6. //  Copyright (c) 2015年 beyond.com All rights reserved. //  不一定要用继承,可以使用分类 #import "MyScrollView.h" #import "CoView.

防止 NSTimer retain 作为 target 的 self

先吐槽一下这个标题,空格略蛋疼,不像中文,但是不写空格看上去则更诡异,求解决方案…… NSTimer会retain它的target,这样如果在控制器当中定义一个NSTimer,target指定为self,则会引起循环引用. 解决方案和防止block引用self一样,第一步需要把NSTimer的操作封装到一个block里,第二步则需要传递一个self的弱引用给block. 首先定义一个NSTimer的分类: 1 #import <Foundation/Foundation.h> 2 3 @int

NSTImer重复执行任务

问题 应用需要调度代码以在特定的时间执行.此外,你还想要重复执行任务. 解决方案 使用NSTimer调度代码以在特定的时间执行.为了使用NSTimer,你需要有日期对象与指向应用的运行循环的引用. 注意: NSTimer需要有运行循环,如果想在Mac或iOS应用中使用定时器,就必须有运行循环.本攻略需要应用带有运行循环.1.11与1.12节分别介绍了创建Mac与iOS应用的步骤. 说明 本攻略的代码位于应用委托中.通常情况下,定时器会放在自定义类或是应用控制器中. 定时器会从特定的日期与时间开始