iOS NSTimer遇坑整理

一、NSTimer使用

const NSTimeInterval TimeInterval = 1.0;

@interface UIViewController ()
// 定義屬性timer
@property (nonatomic, strong) NSTimer *timer;
@end

/**
 * timer 初始化
 * repeats:參數(shù)表示是否重復執(zhí)行(YES表示每TimeInterval秒運行一次function方法。NO表示不重復只調(diào)用 一次,timer運行一次就會自動停止運行)
*/

self.timer =  [NSTimer scheduledTimerWithTimeInterval:TimeInterval target:self selector:@selector(fire) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

注意:將計數(shù)器的repeats設置為YES的時候,self的引用計數(shù)會加1。因此可能會導致self(即VC)不能release,所以在UIViewController delloc前將計數(shù)器timer設置為失效,否則可能會導致內(nèi)存泄露。

//開啟定時器
[self.timer fire];

//暫停定時器(然后再某種情況下再次開啟運行timer)
self.timer.fireDate = [NSDate distantFuture];

//再次開啟定時器
self.timer.fireDate = [NSDate distantPast];

//取消定時器(這個是永久的停止)
[self.timer invalidate];

// 停止后,一定要將timer賦空,否則還是沒有釋放
self.timer = nil;

通常寫到這會遇到諸多問題。

二、NSTimer常見問題

當repeats為YES時timer出現(xiàn)無法釋放的問題(強引用,而非循環(huán)引用引起)

// runloop強引用timer,timer強引用self。如果timer不失效,self就不會釋放,進而造成內(nèi)存泄漏
runloop -> timer -> self(UIViewController)

解決辦法有4種:

  1. 也是最low的方法在控制器消失的時候(viewDidDisappear方法中)設置timer失效,但是會出現(xiàn)一些問題,跳轉下一級界面的時候timer失效,再返回的時候還得在界面出現(xiàn)(viewWillAppear方法中)時從新設置timer,比較繁瑣,控制不好還會問題。不推薦使用

  2. 在UIViewController中調(diào)用didMoveToParentViewController:方法設置timer失效

// 這種方法只有在進入VC時使用的push的方式進入才有效,present進入不會調(diào)用此方法
- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (!parent) {
        [self.timer invalidate];
        self.timer = nil;
    }
}
  1. 利用消息轉發(fā)機制
//利用中間鍵target 不讓timer來強用self
runloop -> timer -> target

示例代碼:

@interface UIViewController ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) id target;
@end

- (void)creatTimer{
    _target = [NSObject new];

    //我們需要使用runtime來給_target添加方法,引入頭文件 #import <objc/runtime.h>
    class_addMethod([_target class], @selector(fire), (IMP)fireIMP, "v@:");

    //timer 的target直接指向_target
    self.timer = [NSTimer scheduledTimerWithTimeInterval:TimeInterval target:_target  selector:@selector(fire) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

void fireIMP(id self, IMP _cmd) {
    NSLog(@"重復跑起來");
}

// 這樣只需要在VC 的dealloc方法中設置timer失效即可
- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
}
  1. 中間鍵弱引用self
// runloop強引用timer,timer強引用proxy, proxy弱引用self。(弱引用不會使self的引用計數(shù)加一)
runloop -> timer -> proxy --> self(UIViewController)

用一個比NSObjec更輕量級的類NSProxy來做中間鍵

示例代碼:

// 創(chuàng)建NSProxy類
#import <Foundation/Foundation.h>

@interface WeakProxy : NSProxy
//使用弱引用
@property (nonatomic, weak) id target;
@end

#import "WeakProxy.h"

@implementation WeakProxy

/**
 * - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
 * - (void)forwardInvocation:(NSInvocation *)invocation
 * 這兩個方法必須寫
*/

// 方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
      return [self.target methodSignatureForSelector:sel];
}

// 消息轉發(fā)
- (void)forwardInvocation:(NSInvocation *)invocation{
    [invocation invokeWithTarget:self.target];
}
@end

回到UIViewController中

- (void)creatTimer{
   // 因為在上述類中沒有寫構造函數(shù)直接alloc。
    WeakProxy *proxy =  [WeakProxy alloc];
    // 弱引用self
    proxy.target = self;

    //timer 的target直接指向proxy
    self.timer =  [NSTimer scheduledTimerWithTimeInterval:TimeInterval target:proxy  selector:@selector(fire) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

// 然后只需在delloc中實現(xiàn)計時器的銷毀即可
- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
}

如果有什么問題請?zhí)岢觯餐懻摻鉀Q。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容