私はこのようなNSTimer
を使用しています:
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(tick) userInfo:nil repeats:YES];
もちろん、NSTimer
は、保持サイクルを作成するターゲットを保持します。さらに、self
はUIViewControllerではないため、タイマーを無効にしてサイクルを中断できるviewDidUnload
のようなものはありません。だから、代わりに弱い参照を使用できるかどうか疑問に思っています:
__weak id weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES];
タイマーmustが無効になっていると聞きました(実行ループから解放することを推測します)。しかし、deallocでそれを行うことができますよね?
- (void) dealloc {
[timer invalidate];
}
これは実行可能なオプションですか?人々がこの問題に対処する多くの方法を見てきましたが、私はこれを見ていません。
提案されたコード:
__weak id weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES];
(i)自己への弱い参照が行われるという効果があります。 (ii)NSTimer
へのポインターを提供するために、弱い参照が読み取られます。弱い参照を持つNSTimer
を作成する効果はありません。そのコードと__strong
参照の使用の唯一の違いは、指定された2行の間にselfが割り当て解除された場合、nil
をタイマーに渡すことです。
最善の方法は、プロキシオブジェクトを作成することです。何かのようなもの:
[...]
@implementation BTWeakTimerTarget
{
__weak target;
SEL selector;
}
[...]
- (void)timerDidFire:(NSTimer *)timer
{
if(target)
{
[target performSelector:selector withObject:timer];
}
else
{
[timer invalidate];
}
}
@end
次に、次のようなことをします:
BTWeakTimerTarget *target = [[BTWeakTimerTarget alloc] initWithTarget:self selector:@selector(tick)];
timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:target selector:@selector(timerDidFire:) ...];
または、クラスメソッドを+scheduledTimerWithTimeInterval:target:selector:...
という形式のBTWeakTimerTargetに追加して、そのコードのよりきれいな形式を作成します。おそらく実際のNSTimer
を公開して、invalidate
できるようにする必要があります。そうでない場合、確立されるルールは次のようになります。
タイマーイベントのミリ秒の精度にそれほど関心がない場合は、NSTimerの代わりにdispatch_after&__weakを使用してこれを行うことができます。コードパターンは次のとおりです。
- (void) doSomethingRepeatedly
{
// Do it once
NSLog(@"doing something …");
// Repeat it in 2.0 seconds
__weak typeof(self) weakSelf = self;
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[weakSelf doSomethingRepeatedly];
});
}
NSTimer @property、無効化/実行ループ、プロキシオブジェクトはありません。単純なクリーンメソッドです。
このアプローチの欠点は、(NSTimer
とは異なり)ブロックの実行時間([weakSelf doSomethingRepeatedly];
を含む)がイベントのスケジューリングに影響することです。
iOS 10およびmacOS 10.12 "Sierra"は新しいメソッドを導入しました- +scheduledTimerWithTimeInterval:repeats:block:
。したがって、次のようにself
を簡単にキャプチャできます。
__weak MyClass* weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer* t) {
MyClass* _Nullable strongSelf = weakSelf;
[strongSelf doSomething];
}];
Swift 3:
_timer = Timer(timeInterval: 1.0, repeats: true) { [weak self] _ in
self?.doSomething()
}
IOS 9以下をターゲットにする必要がある場合(現時点ではこの方法を使用する必要があります)、このメソッドは使用できないため、他の回答でコードを使用する必要があります。
Swift 3
アプリのターゲット<iOS 10:
カスタムWeakTimer( GitHubGist )実装:
final class WeakTimer {
fileprivate weak var timer: Timer?
fileprivate weak var target: AnyObject?
fileprivate let action: (Timer) -> Void
fileprivate init(timeInterval: TimeInterval,
target: AnyObject,
repeats: Bool,
action: @escaping (Timer) -> Void) {
self.target = target
self.action = action
self.timer = Timer.scheduledTimer(timeInterval: timeInterval,
target: self,
selector: #selector(fire),
userInfo: nil,
repeats: repeats)
}
class func scheduledTimer(timeInterval: TimeInterval,
target: AnyObject,
repeats: Bool,
action: @escaping (Timer) -> Void) -> Timer {
return WeakTimer(timeInterval: timeInterval,
target: target,
repeats: repeats,
action: action).timer!
}
@objc fileprivate func fire(timer: Timer) {
if target != nil {
action(timer)
} else {
timer.invalidate()
}
}
}
使用法:
let timer = WeakTimer.scheduledTimer(timeInterval: 2,
target: self,
repeats: true) { [weak self] timer in
// Place your action code here.
}
timer
は標準クラスTimer
のインスタンスであるため、使用可能なすべてのメソッドを使用できます(例:invalidate
、fire
、isValid
、fireDate
など)。timer
インスタンスは、self
が割り当て解除されるか、タイマーのジョブが完了すると割り当て解除されます(例:repeats == false
)。
アプリのターゲット> = iOS 10:
標準タイマーの実装:
open class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: @escaping (Timer) -> Swift.Void) -> Timer
使用法:
let timer = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] timer in
// Place your action code here.
}
SwiftでWeakTimer
ヘルパークラスを定義しました:
/// A factory for NSTimer instances that invoke closures, thereby allowing a weak reference to its context.
struct WeakTimerFactory {
class WeakTimer: NSObject {
private var timer: NSTimer!
private let callback: () -> Void
private init(timeInterval: NSTimeInterval, userInfo: AnyObject?, repeats: Bool, callback: () -> Void) {
self.callback = callback
super.init()
self.timer = NSTimer(timeInterval: timeInterval, target: self, selector: "invokeCallback", userInfo: userInfo, repeats: repeats)
}
func invokeCallback() {
callback()
}
}
/// Returns a new timer that has not yet executed, and is not scheduled for execution.
static func timerWithTimeInterval(timeInterval: NSTimeInterval, userInfo: AnyObject?, repeats: Bool, callback: () -> Void) -> NSTimer {
return WeakTimer(timeInterval: timeInterval, userInfo: userInfo, repeats: repeats, callback: callback).timer
}
}
そして、次のように使用できます:
let timer = WeakTimerFactory.timerWithTimeInterval(interval, userInfo: userInfo, repeats: repeats) { [weak self] in
// Your code here...
}
返されるNSTimer
にはself
への弱い参照があるため、invalidate
でそのdeinit
メソッドを呼び出すことができます。
weakSelfが弱いことは重要ではありません。タイマーはオブジェクトを保持しているため、保持サイクルが残っています。タイマーは実行ループによって保持されるため、タイマーへの弱いポインターを保持することができます(そして、推奨します)。
NSTimer* __weak timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target: self selector:@selector(tick) userInfo:nil repeats:YES];
invalidateについては、正しい方法です。
理論と実践により、トミーのソリューションは機能しません。
理論的には、__ weakインスタンスはパラメーターとして、の実装で
[NSTimer scheduledTimerWithTimeInterval:target:selector: userInfo: repeats:],
ターゲットはまだ保持されます。
Selfを呼び出す弱参照と転送セレクターを保持するプロキシを実装し、プロキシをターゲットとして渡すことができます。 YYWeakProxyなど。
Swift 4バージョン。無効化は、deallocの前に呼び出す必要があります。
class TimerProxy {
var timer: Timer!
var timerHandler: (() -> Void)?
init(withInterval interval: TimeInterval, repeats: Bool, timerHandler: (() -> Void)?) {
self.timerHandler = timerHandler
timer = Timer.scheduledTimer(timeInterval: interval,
target: self,
selector: #selector(timerDidFire(_:)),
userInfo: nil,
repeats: repeats)
}
@objc func timerDidFire(_ timer: Timer) {
timerHandler?()
}
func invalidate() {
timer.invalidate()
}
}
使用法
func startTimer() {
timerProxy = TimerProxy(withInterval: 10,
repeats: false,
timerHandler: { [weak self] in
self?.fireTimer()
})
}
@objc func fireTimer() {
timerProxy?.invalidate()
timerProxy = nil
}
Swift=を使用している場合、ここに自動キャンセルタイマーがあります。
https://Gist.github.com/evgenyneu/516f7dcdb5f2f73d792
タイマーはdeinit
で自動的にキャンセルされます。
var timer: AutoCancellingTimer? // Strong reference
func startTimer() {
timer = AutoCancellingTimer(interval: 1, repeats: true) {
print("Timer fired")
}
}