[英]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
。 這應該刪除指向person
的timer
的強引用。
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 對象將被釋放!
下面的嘗試也是正確的:
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))
首先, invalidate
是NSTimer
類的一個方法,可以用來停止當前正在運行的計時器。 當您將nil
分配給任何對象時,在 ARC 環境中,該變量將釋放該對象。
當你不再需要時停止運行計時器很重要,所以我們寫[timer invalidate]
然后我們寫timer = nil;
以確保它會從內存中丟失其地址,稍后您可以重新創建計時器。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.