解决NSTimer,强引用target引起的无法释放

2020-07-02  本文已影响0人  31313_iOS

NSTimer 是iOS开发中常用的定时器,使用过程中也有一些弊端:

本文分别提供了Swift和Object-C两中开发语言的解决办法。

一、 先补充一下RunLoopMode

1.开发者常用的模式

NSDefaultRunLoopMode: 默认模式,通常主线程是在这个Mode下运行的
UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动的时候不受其他Mode的影响
kCFRunLoopCommonModes :这是一个占位用的Mode,不是一种真正的Mode, 默认包含NSDefaultRunLoopMode、NSModalPanelRunLoopMode、NSEventTrackingRunLoopMode这三个模式。(计时器我们主要使用这个模式)

2.开发者几乎不用的模式

UIInitalizationRunLoopMode: 在刚启动App时进入的第一个Mode,启动完成以后就不再使用
GSEventReceiveRunLoopMode :接受系统事件的内部Mode,通常用不到

通常情况下,我们会把Timer加入到Runloop中启用:

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

二、 解决NSTimer强引用target无法释放

1.更换方法,使用block回调。

使用系统iOS10及以后提供了一个使用block回调,可以避免target无法释放的计时器方法:
(1)、swift版的方法:

 @available(iOS 10.0, *)
    public /*not inherited*/ init(timeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void)

(2)、oc版的方法:

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
2.使用一个中间介来做消息转发。(推荐

(1)、swift版的方法:

import Foundation

class TimerTarget: NSObject {

    weak var target :AnyObject?
    
    convenience init(_ target:AnyObject) {
        self.init()
        self.target = target
    }
    
    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        return target
    }
    
    deinit {
        print("----  deinit ---")
    }
}

具体使用:

func initTimer()  {
     timer = Timer(timeInterval: 1.0, target: TimerTarget(self), selector: #selector(timerStarted), userInfo: nil, repeats: true)
        RunLoop.current.add(timer!, forMode: .common)
}

(2)、oc版的方法:

oc版本使用借助NSProxy这个抽象类,相比NSObject,NSProxy更轻量级, 做消息转发效率更高.必须继承实例化其子类才能使用。

TimerProxy.h文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface TimerProxy : NSProxy

- (instancetype)initWithTarget:(id)target;

+ (instancetype)proxyWithTarget:(id)target;

@end

NS_ASSUME_NONNULL_END

TimerProxy.m文件

#import "TimerProxy.h"

@interface TimerProxy ()

@property (nonatomic, weak) id target;

@end

@implementation TimerProxy

- (instancetype)initWithTarget:(id)target {
    _target = target;
    return self;
}

+ (instancetype)proxyWithTarget:(id)target {
    return [[TimerProxy alloc] initWithTarget:target];
}

- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}

@end

具体使用:

- (void)initTimer {
    TimerProxy * proxy = [TimerProxy proxyWithTarget:self];
    self.timer = [NSTimer timerWithTimeInterval:1.0 target:proxy selector:@selector(timerStart) userInfo:nil repeats:YES];//NSRunLoopCommonModes
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

亲测有效,已结束!!!

上一篇下一篇

猜你喜欢

热点阅读