为啥在视图控制器被取消初始化并导致 SIGABRT 或 EXC_BAD_ACCESS 后观察者仍然活着?
Posted
技术标签:
【中文标题】为啥在视图控制器被取消初始化并导致 SIGABRT 或 EXC_BAD_ACCESS 后观察者仍然活着?【英文标题】:Why observer stay alive after a view controller is deinitialized and causes SIGABRT or EXC_BAD_ACCESS?为什么在视图控制器被取消初始化并导致 SIGABRT 或 EXC_BAD_ACCESS 后观察者仍然活着? 【发布时间】:2018-08-07 20:33:49 【问题描述】:我有一个带有两个视图控制器的简单应用程序,第一个带有一个按钮来显示第二个,它使用 AVPlayerViewController 和 AVPlayer 从 URL 播放视频。在第二个视图控制器的 viewDidAppear() 中,我初始化 AVPlayerViewController,配置 AVPlayer 并向 AVPlayer 添加观察者以检测视频何时开始播放。
如果我在视频开始之前手动关闭第二个视图控制器(它必须迅速关闭,使用慢速 Internet 连接可以帮助做到这一点)我得到一个 EXC_BAD_ACCESS 因为它似乎调用了 observeValue() 但在此方法中访问的变量(此处为容器视图)不再存在。
我必须删除 deinit 中的观察者才能解决此问题。 当我尝试在 viewWillDisappear() 等其他地方删除它时,有时甚至没有添加观察者,所以我在删除时得到一个 SIGABRT(奇怪,因为我在 viewDidAppear() 中添加了它):
'Cannot remove an observer <App.SecondViewController 0x101805600> for the key path "status" from <AVPlayer 0x1c801a0b0> because it is not registered as an observer.'
有人知道原因吗?
而苹果documentation 也说:
接收此消息的对象和观察者都不会被保留。
为什么我需要在 deinit 中移除观察者?换句话说,在我的例子中,为什么观察者在 deinit 之后仍然活着?
这是第二个视图控制器的代码(第一个视图控制器只有一个 UIButton 用于显示 segue 并嵌入在 NavigationController 中):
import UIKit
import AVKit
class SecondViewController: UIViewController
@IBOutlet weak var containerView: UIView!
var player: AVPlayer!
deinit
//player.removeObserver(self, forKeyPath: "status")
override func viewDidLoad()
super.viewDidLoad()
override func viewDidAppear(_ animated: Bool)
super.viewDidAppear(animated)
let url = URL(string: "http://clips.vorwaerts-gmbh.de/VfE_html5.mp4")
// Create the player and player view controller.
player = AVPlayer(url: url!)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
// Add observer to detect when the video start playing.
player.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
// Add the player controller in the container view.
self.addChildViewController(playerViewController)
self.containerView.addSubview(playerViewController.view)
playerViewController.view.frame = containerView.bounds
playerViewController.didMove(toParentViewController: self)
player.play()
override func viewWillDisappear(_ animated: Bool)
super.viewDidDisappear(animated)
player.removeObserver(self, forKeyPath: "status")
override func viewDidDisappear(_ animated: Bool)
super.viewDidDisappear(animated)
//player.removeObserver(self, forKeyPath: "status")
override func didReceiveMemoryWarning()
super.didReceiveMemoryWarning()
print("SndVC - Memory Warning")
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
if keyPath == "status"
// Perform a task on the container view.
self.containerView.backgroundColor = UIColor.purple
【问题讨论】:
deinit 调用 ???? 是的,我打电话给 deinit 来解决这个问题,但是我想知道为什么我需要删除 deinit 中的观察者,因为 Apple 文档说因为 ios 9 是它自动取消注册并且仅当它是基于块的观察者时需要取消注册。 【参考方案1】:如果不实际调试应用程序,很难说它为什么会崩溃。但我会在 viewWillAppear
/ viewDidDisappear
或 init
和 dealloc
中配对 addObserver
/ removeObserver
。
(我一般不会在viewDidLoad
中放入任何KVO 代码,因为它没有来自UIKit
的合作伙伴回调)
PS:你也错过了super.viewDidAppear()
的电话
【讨论】:
我能够使用 EXC_BAD_ACCESS 进行的唯一调试是使用 Zombie 对象,我得到:[App.SecondViewController retain]: message sent to deallocated instance 0x10a5012a0。 KVO 代码在 viewDidAppear() 中是问题中的错误,您仍然认为在这里使用它是一个坏主意吗?我无法找到另一种观察 AVPlayer 的方法。顺便说一句,谢谢你指出我在 viewDidAppear() 上的错误。以上是关于为啥在视图控制器被取消初始化并导致 SIGABRT 或 EXC_BAD_ACCESS 后观察者仍然活着?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的 UIViewController 初始化程序从未被调用?