簡體   English   中英

iOS 中的 self.timer = nil 與 [self.timer invalidate] 有什么區別?

[英]What is difference between self.timer = nil vs [self.timer invalidate] in iOS?

誰能解釋我self.timer=nil[self.timer invalidate]

self.timer的內存位置究竟發生了什么?

在我的代碼中

self.timer=nil

不會停止計時器但是

[self.timer invalidate]

停止計時器。

如果您需要我的代碼,我也會更新。

一旦您不需要運行計時器,就使計時器對象無效,之后無需取消其引用。

這就是 Apple 文檔所說的: NSTimer

一旦在運行循環上調度,計時器就會在指定的時間間隔內觸發,直到它失效。 非重復定時器在觸發后立即失效。 但是,對於重復計時器,您必須自己通過調用其 invalidate 方法來使計時器對象無效。 調用該方法請求從當前運行循環中移除定時器; 因此,您應該始終從安裝計時器的同一線程調用 invalidate 方法。 使計時器無效會立即禁用它,以便它不再影響運行循環。 然后運行循環移除計時器(以及它對計時器的強引用),要么是在 invalidate 方法返回之前,要么是在稍后的某個時間點。 一旦失效,定時器對象就不能再使用。

其他答案中沒有提到一個關鍵區別。

為了測試這個,在 Playground 中刪除以下代碼。

第一次嘗試:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class Person{
    var age = 0
    lazy var timer: Timer? = {
        let _timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
        return _timer
    }()

    init(age: Int) {
        self.age = age
    }

    @objc func fireTimer(){
        age += 1
        print("age: \(age)")
    }

    deinit {
        print("person was deallocated")
    }
}

// attempt:
var person : Person? = Person(age: 0)
let _ = person?.timer
person = nil

所以讓我問你一個問題。 在代碼的最后一行,我只是將person設置為nil 這意味着person對象被釋放,它的所有屬性都設置為nil並從內存中刪除。 對?

只要沒有其他對象持有對它的強引用,一個對象就會被釋放。 在我們的例子中, timer仍然持有對 person 的引用,因為運行循環對定時器§有一個強引用,因此person對象不會被釋放。

上面代碼的結果是還是繼續執行! 讓我們修復它。


第二次嘗試:

讓我們將計時器設置為nil 這應該刪除指向persontimer的強引用。

var person : Person? = Person(age: 0)
let _ = person?.timer
person?.timer = nil
person = nil

錯誤的! 我們只刪除指向timer指針。 然而上面代碼的結果就像我們最初的嘗試一樣。 它仍然繼續執行......因為運行循環仍然針對/引用self


那么我們需要做什么呢?

很高興你問。 我們必須invalidate計時器invalidate

第三次嘗試:

var person : Person? = Person(age: 0)
let _ = person?.timer

person?.timer = nil
person?.timer?.invalidate()
person = nil

這看起來更好,但它仍然是錯誤的。 你能猜到為什么嗎?

我給你一個提示。 看下面的代碼👇。


第四次嘗試(正確)

var person : Person? = Person(age: 0)
let _ = person?.timer

person?.timer?.invalidate()
person?.timer = nil
person = nil
// person was deallocated

我們的第四次嘗試和我們的第三次嘗試一樣,只是代碼的順序不同。

person?.timer?.invalidate()刪除了運行循環對其目標即self強引用,現在如果指向person的指針被刪除......我們的 person 對象將被釋放!

下面的嘗試也是正確的:

第 5 次嘗試(正確)

var person : Person? = Person(age: 0)
let _ = person?.timer

person?.timer?.invalidate()
person = nil
// person was deallocated

請注意,在我們的第 5 次嘗試中,我們沒有將計時器設置為nil 但是 Apple 建議我們這樣做:

一旦失效,定時器對象就不能再使用。

請參閱任務管理 - 計時器

將其設置為nil也是代碼其他部分的指示符 它有助於我們檢查它,如果它不是nil那么我們就會知道計時器仍然有效,並且周圍沒有無意義的對象。

在使計時器無效后,您應該將nil分配給變量,否則該變量將指向一個無用的計時器。 內存管理和 ARC 與為什么要將其設置為nil無關。 在使計時器無效后, self.timer現在正在引用一個無用的計時器。 不應進一步嘗試使用該值。 將其設置為nil可確保任何進一步嘗試訪問 self.timer 都將導致nil

來自 rmaddy 上面的評論

話雖如此,我認為isValid是一種更有意義的方法,就像isEmpty比執行array.count == 0更有意義和更有效array.count == 0 ......


那么為什么第三次嘗試不正確呢?

因為我們需要一個指向計時器的指針,以便我們可以使其無效。 如果我們將該指針設置為nil那么我們就會失去指向它的指針。 運行循環仍然保持指向它的指針時,我們丟失了它! 因此,如果我們想關閉計時器,我們應該我們失去對它的引用之前(即在我們將其指針設置為nil )使其invalidate ,否則它將成為廢棄的內存( 而不是泄漏)。

結論:

  • 要正確停止計時器,您必須使用invalidate 在使timer invalidate之前invalidate其置nil
  • 在您使timer無效后,將其設置為nil以免被重用。
  • 調用invalidate將刪除運行循環指向self的指針。 只有這樣,包含計時器的對象才會被釋放。

那么當我實際構建應用程序時,這如何適用?

如果您的 viewController 具有person屬性,然后您將這個 viewController 從導航堆棧中彈出,那么您的 viewController 將被釋放。 在其deinit方法中,您必須使該人的計時器無效。 否則,由於運行循環,您的 person 實例將保留在內存中,並且它的計時器操作仍要執行! 這可能會導致崩潰!

更正:

感謝羅布的回答

如果您正在處理重復的 [NS] 計時器,請不要嘗試在 [NS] 計時器的所有者的 dealloc 中使它們無效,因為在解決強引用循環之前,dealloc 顯然不會被調用。 例如,在 UIViewController 的情況下,您可以在 viewDidDisappear 中執行此操作

話雖如此, viewDidDisappear可能並不總是正確的位置,因為如果你只是在它上面推一個新的 viewController, viewDidDisappear也會被調用。 你基本上應該從不再需要它的角度來做。 你明白了...


§:因為運行循環維護計時器,從對象生命周期的角度來看,通常不需要在您安排計時器保留對計時器的引用。 (因為當您將計時器的方法指定為選擇器時,計時器作為參數傳遞,您可以在該方法中適當時使重復計時器無效。)但是,在許多情況下,您還需要使計時器無效的選項 - 甚至可能在此之前開始。 在這種情況下,您確實需要保留對計時器的引用,以便您可以在適當的時候停止它。


所有的功勞都歸功於我的同事布蘭登:

專家提示:

即使您沒有重復計時器,如果您使用選擇器函數,Runloop [如文檔中所述] 將持有對您的目標的強引用,直到它觸發,之后它將釋放它。

但是,如果您使用基於塊的函數,那么只要您在塊內弱指向 self ,那么 runloop 就不會保留self 但是它會繼續執行,因為沒有調用invalidate

如果你不使用[weak self]那么基於塊的行為就像選擇器類型一樣,它會在它被觸發后釋放self

將以下代碼粘貼到 Playground 中,看看有什么不同。 選擇器版本將觸發后被釋放。 塊基將在解除分配解除分配。 基本上,一個的生命周期由 runloop 管理,而另一個則由對象本身管理

@objc class MyClass: NSObject {
    var timer: Timer?

    func startSelectorTimer() {
        timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(MyClass.doThing), userInfo: nil, repeats: false)
    }

    func startBlockTimer() {
        timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false, block: { [weak self] _ in
            self?.doThing()
        })
    }

    @objc func doThing() {
        print("Ran timer")
    }

    deinit {
        print("My Class deinited")
    }
}

var mySelectorClass: MyClass? = MyClass()
mySelectorClass?.startSelectorTimer()
mySelectorClass = nil // Notice that MyClass.deinit is not called until after Ran Timer happens
print("Should have deinited Selector timer here")

RunLoop.current.run(until: Date().addingTimeInterval(7))

print("---- NEW TEST ----")

var myBlockClass: MyClass? = MyClass()
myBlockClass?.startBlockTimer()
myBlockClass = nil // Notice that MyClass.deinit IS called before the timer finishes. No need for invalidation
print("Should have deinited Block timer here")

RunLoop.current.run(until: Date().addingTimeInterval(7))

首先, invalidateNSTimer類的一個方法,可以用來停止當前正在運行的計時器。 當您將nil分配給任何對象時,在 ARC 環境中,該變量將釋放該對象。

當你不再需要時停止運行計時器很重要,所以我們寫[timer invalidate]然后我們寫timer = nil; 以確保它會從內存中丟失其地址,稍后您可以重新創建計時器。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM