为啥在视图控制器被取消初始化并导致 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 initdealloc 中配对 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 后观察者仍然活着?的主要内容,如果未能解决你的问题,请参考以下文章

show segue 后我的视图控制器没有被取消初始化

为啥我的 UIViewController 初始化程序从未被调用?

线程 1:单击按钮时发出 SIGABRT 信号

为啥我取消选中内容布局指南后通过了自动布局检查

为啥我的方法在 iPhone/iPod 上被调用了两次,但在 iPad 上却没有?

为啥当我在目标 c 中重新加载表视图时选择的值会被取消选择