NSTimer 循環引用問題

題記

在iOS 10系統以前,系統的NSTimer是會引發循環引用的,致使內存泄漏。下面就針對這個問題給出幾種解決方法。ios

在iOS 10之後系統,蘋果針對NSTimer進行了優化,使用Block回調方式,解決了循環引用問題。git

//API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
    [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
       // Do some things
    }];
複製代碼

這個iOS 10方法能解決循環引用問題。可是咱們目前大部分仍是要適配iOS10如下系統。github

平時咱們都是這麼使用NSTimermacos

[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(testTimer) userInfo:nil repeats:YES];

    //或者
    self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(testTimer) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.myTimer forMode:NSDefaultRunLoopMode];

- (void)testTimer {
    NSLog(@"Do Some Things");
}

//中止Timer
- (void)dealloc {
    [self.myTimer invalidate];
    self.myTimer = nil;
}

複製代碼
可是上面你有沒有發現, 這個dealloc 方法根本不會調用,只要程序沒殺死,就不會執行。造成了大的死循環。由於self.timer  與 NSTimer 的target:self(VC) 相互強引用,沒有釋放,怎麼可能執行dealloc。

五種方式解決NSTimer循環引用問題

方法一:

使用(void)didMoveToParentViewController:(UIViewController *)parent方法,在這個方法裏清掉定時器,就會釋放Timer對象,也就解決了強引用。即調用dealloc方法了。bash

注意:針對PresentVC 不適用。oop

//生命週期  移除childVC的時候
- (void)didMoveToParentViewController:(UIViewController *)parent {
    if (parent == nil) {
        [self.myTimer invalidate];
        self.myTimer = nil;
    }
}
複製代碼

運行結果

方法二 中間件方法

消息傳遞沒有什麼是中間件不能解決的,若是有,那就在加中間件 O(∩_∩)O哈哈~優化

//定義箇中間件屬性
@property (nonatomic, strong) id target;

    _target = [NSObject new];
    class_addMethod([_target class], @selector(testTimer), (IMP)timerIMP, "v@:");
    //這裏換成_target  不用self了。  這就沒有了循環引用了
    self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(testTimer) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.myTimer forMode:NSDefaultRunLoopMode];

void timerIMP(id self, SEL _cmd) {
    NSLog(@"Do some things");
}

//中止Timer
- (void)dealloc {
    [self.myTimer invalidate];
    self.myTimer = nil;

    NSLog(@"Timer dealloc");
}
複製代碼

此時也是能夠的。ui

中間件方法

方法三:使用NSProxy類

新建一個類TimerProxy,繼承NSProxy。atom

設置一個屬性spa

//注意這裏要使用weak
@property (nonatomic, weak) id target;
複製代碼

而後在實現兩個方法:

/** 方法簽名 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}
/** 消息轉發 */
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}

在你須要的地方,而後導入TimerProxy頭文件使用
@property (nonatomic, strong) TimerProxy *timerProxy;
    _timerProxy = [TimerProxy alloc];//注意這裏只有alloc方法
    _timerProxy.target = self;
    self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_timerProxy selector:@selector(testTimer) userInfo:nil repeats:YES];

複製代碼

這時也能夠解決循環引用問題。

方法四 仿照系統iOS 10 Block方法

新建一個NSTimer分類, QLTimer. 定義一個加方法

/** 定義一個加方法 */
+ (NSTimer *)QLscheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats block:(void(^)(void))timerBlock;

//實現方法
+(NSTimer *)QLscheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats block:(nonnull void (^)(void))timerBlock {
    return [self scheduledTimerWithTimeInterval:timeInterval
                                         target:self
                                       selector:@selector(QLTimerHandle:)
                                       userInfo:[timerBlock copy] //注意copy
                                        repeats:repeats];
}

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

使用
    __block typeof(self) weakSelf = self;
    self.myTimer = [NSTimer QLscheduledTimerWithTimeInterval:1.0 repeats:YES block:^{
        __block typeof(weakSelf) strongSelf = weakSelf;
        [strongSelf testTimer];
    }];
複製代碼

方法五

聽說是通過蘋果官方確認過的方法:used with GCD queues.

MSWeakTimer

以上方法均可以解決NSTimer 循環引用問題。暫時就這麼多,其餘方法但願留言補充。謝謝!
相關文章
相關標籤/搜索