mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
iOS: stop a Low Power Mode change crashing the video background
Toggling Low Power Mode with a video wallpaper on screen kills the app. Foundation posts NSProcessInfoPowerStateDidChange from whatever queue it likes. The handler is @objc on a UIView subclass, so it is main actor isolated, and this target builds in Swift 6 language mode. That combination does not race, it traps: the @objc thunk checks the executor and aborts before the body runs at all. The other five observers in this file are UIKit lifecycle notifications and really do arrive on main, which is why only this one goes bang. So the @objc entry point becomes nonisolated and hops, and the body it used to be stays main isolated. The seek completion handler a few lines above already does exactly this, so there was a pattern in the file to follow. The capture is weak on purpose. A strong one could leave the final release on that Task's thread, and deinit here assumes it is on main, which would trade one trap for another. Registration stays selector based. Switching to the block form returns a token that removeObserver(self) does not unregister, and teardown relies on that one call clearing all six. Needs a video wallpaper set, so it is not every install, but the automatic prompt at 20% battery fires it without the user doing anything.
This commit is contained in:
@@ -123,7 +123,7 @@ private final class LoopingVideoView: UIView {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
adaptToPowerState()
|
||||
applyPowerState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +214,21 @@ private final class LoopingVideoView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func adaptToPowerState() {
|
||||
// Every other notification here is a UIKit lifecycle one and arrives on the main
|
||||
// thread. This one does not: Foundation posts the power-state change from whatever
|
||||
// queue it likes, and the body below touches UIKit and the player. The class is a
|
||||
// UIView so it is main-actor isolated, which under Swift 6 means the @objc entry
|
||||
// point checks the executor and traps before the body even runs. So take the hit
|
||||
// off the main thread and hop, the way the seek handler above already does.
|
||||
@objc private nonisolated func adaptToPowerState() {
|
||||
Task { @MainActor [weak self] in
|
||||
self?.applyPowerState()
|
||||
}
|
||||
}
|
||||
|
||||
// Weakly, because a strong capture could leave the last release on that Task's
|
||||
// thread, and deinit assumes it is on main.
|
||||
private func applyPowerState() {
|
||||
if ProcessInfo.processInfo.isLowPowerModeEnabled {
|
||||
pause()
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user