解决NSTimer,强引用target引起的无法释放
2020-07-02 本文已影响0人
31313_iOS
NSTimer 是iOS开发中常用的定时器,使用过程中也有一些弊端:
1. 受到RunLoopde影响出现计时准确性问题(很多大佬都转而使用GCD计时器)
2. 对target的强引用而导致页面无法释放的问题。
本文分别提供了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];
}
亲测有效,已结束!!!