Слабая спасылка на NSTimer Мэта Прадухіліць Захаваў цыкл

Я выкарыстоўваю NSTimer , як гэта:

timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(tick) userInfo:nil repeats:YES];

Вядома, NSTimer захоўвае мэта, якая стварае захаваць цыкл. Акрамя таго, самастойна ня UIViewController, так што я нічога не маю, як viewDidUnload , дзе я магу прывесці да адмены таймера разарваць заганны круг. Так што мне цікава, калі я мог бы выкарыстаць слабую спасылку замест:

__weak id weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES];

Я чуў, што таймер <�моцны> павінен быць прызнаныя несапраўднымі (я думаю, каб вызваліць яго ад цыклу выканання). Але мы маглі б зрабіць гэта ў нашым dealloc, праўда?

- (void) dealloc {
    [timer invalidate];
}

Ці з'яўляецца гэта жыццяздольны варыянт? Я бачыў шмат спосабаў, якімі людзі маюць справу з гэтым пытаннем, але я не бачыў гэтага.

46
Акрамя прыведзеных ніжэй адказаў, ніхто не тлумачыць, чаму несапраўднасць таймера ў dealloc бескарысна (ад тут ): таймер падтрымлівае моцную спасылку на сваёй мэты. Гэта азначае, што да таго часу, як таймер застаецца ў сіле, яго мэта не будзе вызваляцца. Як следства, гэта азначае, што яна не мае сэнсу для мэтавага таймера, каб паспрабаваць ануляваць таймер ў dealloc метад-метадзе dealloc не будзе выклікацца да тых часоў, як таймер дзейнічае.
дададзена аўтар guyarad, крыніца

8 адказы

Прапанаваны код:

__weak id weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES];

мае той эфект, што (я) слабая спасылка зробленая на сябе; (II), што слабая спасылка счытваецца для таго, каб забяспечыць паказальнік на NSTimer . Гэта не будзе мець эфект стварэння NSTimer са слабай спасылкай. Адзінае адрозненне паміж гэтым кодам і выкарыстоўваючы __ моцны спасылачныя з'яўляецца тое, што калі самасць вызвалены паміж двума лініямі дадзеных, то вы будзеце ехаць 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:) ...];

Ці нават дадаць метад класа ў BTWeakTimerTarget кшталту + scheduledTimerWithTimeInterval: мэты: селектар: ... , каб стварыць акуратней форму гэтага кода. Вы, верагодна, хочаце, каб выставіць рэальны NSTimer , так што вы можаце Invalidate гэта, у адваротным выпадку правілы, устаноўленыя будзе:

    <�Літый> рэальная мішэнь не ўтрымліваюцца з дапамогай таймера;
  1. таймер будзе спрацоўваць адзін раз пасля таго, як рэальная мэта пачалася (і, верагодна, завершана) вызваленне, але што стральба будзе ігнаравацца і таймер несапраўднай то.
70
дададзена
@sftsz Вы карыстаецеся runloop для запуску таймера?
дададзена аўтар mabounassif, крыніца
Awesome, дзякуй. Я ў канчатковым выніку, зрабіўшы NSWeakTimer абалонкі, якія апрацоўваюцца селектар праводкі gist.github.com/bendytree/5674709
дададзена аўтар bendytree, крыніца
@bendytree я разгледзеў свой код. Мэту не павінна быць правапераемнікам ўласцівасць. Хутчэй за ўсё, ён павінен быць слабы ўласцівасць, як апісана @Tommy. Прызначаюць ўласцівасці не становяцца нуль пасля deallocating, у той час як слабыя ўласцівасці робяць. Такім чынам, ваш , калі (мэта) праверка ніколі не стане праўдай.
дададзена аўтар Pwner, крыніца
Я проста паспрабаваў @ рашэнне bendytree і я пачухаў галаву назаўжды, пакуль я не выявіў, што вы не перадаяце аб'ект для вашага выбару ў метадзе «агонь». Маё прыкладанне выкарыстоўвае паўтараюцца таймеры і анулюе іх у селектары названы «вагонь», але толькі калі Вы ўмова задавальняецца. Як ня быў прыняты ні адзін аб'ект, таймер ніколі не быў адменены, і пабег назаўжды. Той, хто капіяваць/ўставіць код павінен пераканацца, што выкарыстоўваць «withObject: таймер» замест «withObject: нуль». У любым выпадку, дзякуй за сниппета !!
дададзена аўтар guitarflow, крыніца
Дзіўнае рашэнне.
дададзена аўтар Yinfeng, крыніца
@Pwner Я праверыў яго і змяніў прызначыць слабы, але ўсё ж ён ніколі не анулюе яго. што я павінен рабіць?
дададзена аўтар sftsz, крыніца

If you are not that concerned about the millisecond accuracy of the timer events, you could use dispatch_after & __weak instead of NSTimer to do this. Here's the code pattern:

- (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, ня Invalidate/runloop рэчы і няма проксі-аб'ект, толькі просты чысты метад.

Недахопам гэтага падыходу з'яўляецца тое, што (у адрозненне ад NSTimer ) падчас выканання блока (які змяшчае [weakSelf doSomethingRepeatedly]; ) будзе ўплываць на планаванне падзей.

26
дададзена
Выдатная ідэя. І калі вы хочаце, каб паўтарыць, вы можаце праверыць, калі weakSelf! = Нуль і калі гэта так, проста рэкурсіўна выклікаем dispatch_after ўнутры блока дастаўкі. Адзіным недахопам з'яўляецца тое, што робіць яго цяжка змяніць таймер (напрыклад, змяніць інтэрвал) пасля таго, як ён быў створаны.
дададзена аўтар devios1, крыніца
Будзьце асцярожныя пры выкананні паўтаральнага таймера з дапамогай гэтага метаду. Пры выкарыстанні рэгулярнай паўтараецца NSTimer, працягласць выканання мэтавага выбару не будзе ўплываць на ваш час. У гэтым выпадку, калі вы не зробіце іншы dispatch_after адразу, калі таймер выклікаецца (і нават так) вы будзеце мець перакосаў сінхранізацыі.
дададзена аўтар guyarad, крыніца

iOS 10 and macOS 10.12 "Sierra" introduced a new method, +scheduledTimerWithTimeInterval:repeats:block:, so you could capture self weakly simply as:

__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 або ніжэй (які вы павінны ў гэты момант), гэты метад не можа быць выкарыстаны, так што вам яшчэ трэба будзе выкарыстоўваць код у іншых адказах.

17
дададзена
Гэта аленевая скура »праца да таймера анулюецца, нават калі сам з'яўляецца слабым
дададзена аўтар protspace, крыніца

<�Моцны> Swift 3

App target < iOS 10:

Custom WeakTimer (GitHubGist) implementation:

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 is instance of standard class Timer, so you can use all available methods (e.g. invalidate, fire, isValid, fireDate and etc).
timer instance will be deallocated when self is deallocated or when timer's job is done (e.g. repeats == false).

App target >= iOS 10:
Standard Timer implementation:

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.
}
4
дададзена
@mkirk, Так, але Таймер гэта адзіны чалавек, які трымае моцную спасылку на WeakTimer . Гэта азначае, што WeakTimer Асобнік жыве да таго часу, Таймер жыве. <�Код> Таймер жыве да моманту, калі fileprivate FUNC агонь называецца і мэтавай ужо памёр. <�Код> мэта мае не уласнага жыцця, ніхто ў вышэй вядро захоўвае моцную спасылку на мэтавай .
дададзена аўтар Vlad Papko, крыніца
Але не проста «прайшлі даляр» на WeakTimer , паколькі WeakTimer # INIT Зараз пераходзіць Таймер моцная спасылка на мэта: самастойна ?
дададзена аўтар mkirk, крыніца

У 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 мае слабую спасылку на Самаўдасканаленне , так што вы можаце назваць яго Invalidate метад Deinit .

4
дададзена
@pwightman Doh, вялікі ўлоў! Я абнавіў код у маёй абароне. (Гэтак жа, як код у маёй РЭПО ... Я заўсёды перадавала ў NIL для USERINFO , так што я цалкам прапусціў гэта.) Дзякуй!
дададзена аўтар shadowmatter, крыніца
Я не разумею, як адзін, мяркуюць, каб выканаць таймер, так як ваш блок кода не выканаць яго
дададзена аўтар thibaut noah, крыніца
Вы хацелі б перадаць USERINFO у NSTimer канструктара, не?
дададзена аўтар pwightman, крыніца
weakTimer вярнуўся з статычнага метаду не будзе захоўваць сябе, калі яна не паўтараецца. Так што гэта працуе толькі ў рэжыме паўтору. І гэта павінна дадаць да runloop ўручную.
дададзена аўтар leavez, крыніца
Дзякуй. Гэта працуе ў маім Swift праекта!
дададзена аўтар Tualatrix Chou, крыніца
гэта прыводзіць да праблем ўцечкі Праверкі (профіль у інструментах). паспрабуйце гэта gist.github.com/onevcat/2d1ceff1c657591eebde
дададзена аўтар Aleksey Tsyss, крыніца

Гэта не важна, што <�ет> weakSelf слабая, таймер ўсё яшчэ захоўвае аб'ект такім чынам, ёсць усе яшчэ захоўваюць цыкл. Паколькі таймер утрымліваецца цыкл выканання, вы можаце (і я прапаную) ўтрымліваць слабы паказальнік таймера:

NSTimer* __weak timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target: self selector:@selector(tick) userInfo:nil repeats:YES];

Аб Invalidate вы спосаб зрабіць гэта правільна.

3
дададзена
Прынамсі, гэта дазваляе пазбегнуць захавання цыклу. Я да гэтага часу лічу, што з дапамогай слабога таймера лепш.
дададзена аўтар Ramy Al Zuhouri, крыніца
Таймер Кіраўніцтва па праграмаванні @Mecki ад Apple не згодны з вамі: developer.apple.com/library/content/documentation/Cocoa/…
дададзена аўтар frangulyan, крыніца
@Tommy правільна, гэта не пазбегнуць цыклу захавання. Да таго часу, пакуль таймер ня анулюецца аб'ект ніколі не памрэ, і калі толькі dealloc анулюе таймер, ён ніколі не будзе прызнаны несапраўдным небудзь. Слабое таймер не мае сэнсу і амаль ніколі не тое, што вы хочаце.
дададзена аўтар Mecki, крыніца
@frangulyan Не, гэта не так. Ён кажа: «<�я> Паколькі цыкл запуску падтрымлівае таймер », што азначае, што runloop трымае таймер жывы. Гэта не мае значэння, калі вы трымаеце яго ў жывых, ён не памрэ да таго часу, як гэта запланавана, і афіцыйны спосабам запланаванага таймера з'яўляецца несапраўдным яе. Ён таксама кажа, што «<�я> Таймер падтрымлівае моцную спасылку на сваю мэту. », бо пакуль таймер жывы, ён будзе трымаць сваю мэту ў жывых, таксама. Так што робіць таймер слабых выпраўленняў нічога, калі вы не анулюе яго ў нейкі момант.
дададзена аўтар Mecki, крыніца
Але тады не бы runloop захаваць моцную спасылку на таймер, таймер захаваць моцную спасылку на я і dealloc не адбываецца?
дададзена аўтар Tommy, крыніца
Таймер можа таксама выклікаць селектар weakSelf.
дададзена аўтар delta2flat, крыніца

Калі вы выкарыстоўваеце Swift тут з'яўляецца аўтаматычнай адмены таймера:

https://gist.github.com/evgenyneu/516f7dcdb5f2f73d7923

Таймер адмяняе сябе аўтаматычна Deinit .

var timer: AutoCancellingTimer?//Strong reference

func startTimer() {
  timer = AutoCancellingTimer(interval: 1, repeats: true) {
    print("Timer fired")
  }
}
0
дададзена

Swift 4 версіі. Invalidate павінен выклікацца перад 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
}
0
дададзена