Ios AVPlayer和观察器的去初始化失败导致内存泄漏
2020年10月编辑:以前的标题是“UIPageViewController的UIViewController中未调用Deinitializer” 我希望以下UIViewController中的deinitializer(它是UIPageViewController的一部分)删除我的Ios AVPlayer和观察器的去初始化失败导致内存泄漏,ios,swift,uiviewcontroller,uipageviewcontroller,Ios,Swift,Uiviewcontroller,Uipageviewcontroller,2020年10月编辑:以前的标题是“UIPageViewController的UIViewController中未调用Deinitializer” 我希望以下UIViewController中的deinitializer(它是UIPageViewController的一部分)删除我的playerLayer,并将player设置为nil,这样内存就不会过载(因为当UIViewController被认为不再需要时,应该始终调用deinit): 为了检查是否执行过deinit,我添加了打印,发现它从未
playerLayer
,并将player
设置为nil
,这样内存就不会过载(因为当UIViewController被认为不再需要时,应该始终调用deinit
):
为了检查是否执行过deinit
,我添加了打印,发现它从未被调用过。有人能解释为什么不叫它吗?你会建议我做什么来实现我想做的
编辑:
按照Rob(在评论中)的建议,我发现以下函数导致内存泄漏。如果可以在documents目录中找到文件,则该函数应设置播放机
setupPlayer()函数:
//setup video player
func setupPlayer() {
//get name of file on server //self.video is a String containing the URL for a video on a server
let fileName = URL(string: self.video!)!.lastPathComponent
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let filePath = url.appendingPathComponent(fileName)?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath!) {
//create file with name on server if not there already
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
if let docDir = paths.first
{
let appFile = docDir.appending("/" + fileName)
let videoFileUrl = URL(fileURLWithPath: appFile)
//player's video
if self.player == nil {
let playerItemToBePlayed = AVPlayerItem(url: videoFileUrl) //AVPlayerItem(url: videoFileUrl)
self.player = AVPlayer(playerItem: playerItemToBePlayed)
//add sub-layer
playerLayer = AVPlayerLayer(player: self.player)
playerLayer.frame = self.view.frame
self.controlsContainerView.layer.insertSublayer(playerLayer, at: 0)
//when are frames actually rendered (when is video loaded)
self.player?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context:nil)
//loop through video
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
})
}
}
}
}
pageViewController功能(viewcontrollerAfter)
func pageViewController(\pageViewController:UIPageViewController,viewController之后的viewController:UIViewController)->UIViewController?
{
让currentIndexString=(viewController为!MyViewController).index
让currentIndex=indec.index(of:currentIndexString!)
//如果是,则设置下一页
如果currentIndex!
编辑2:
正如您所看到的,没有回溯,请注意这个malloc(右上)和类似的大malloc(左下)的大小
如果我们查看“调试内存图”中的对象图,我们可以看到: 我们可以看到视图控制器被闭包(中间路径)捕获。我们还可以看到观察者保留了一个强引用(即底部路径) 因为我打开了“Malloc stack”功能(如中所示),所以我可以单击“Closure Captures”并在右侧面板中看到堆栈跟踪: (请原谅,该内存图与第一个屏幕快照略有不同,因为我修复了另一个内存问题,即观察者,如本答案末尾所述。) 无论如何,如果我单击堆栈跟踪中最高的黑色条目(即,堆栈跟踪中我自己的代码的最后一位),它会直接将我们带到有问题的代码: 这让我们注意到您的原始代码:
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
})
闭包中强烈引用了self
。您可以通过以下方法更正此问题:
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
注意,闭包中的[weak self]
捕获列表
顺便说一下,虽然您不需要在
deinit
中nil
您的player
,但您确实需要删除观察员。我还为您的观察者设置了一个context
,这样您的observer值(forKeyPath:of:change:context:)
就可以知道它是否需要处理
因此,这可能会导致如下结果:
private var observerContext = 0
private weak var observer: NSObjectProtocol?
func setupPlayer() {
let fileName = URL(string: video!)!.lastPathComponent
let fileManager = FileManager.default
let videoFileUrl = try! fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent(fileName)
if fileManager.fileExists(atPath: videoFileUrl.path), player == nil {
let playerItemToBePlayed = AVPlayerItem(url: videoFileUrl)
player = AVPlayer(playerItem: playerItemToBePlayed)
//add sub-layer
playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = view.bounds
controlsContainerView.layer.insertSublayer(playerLayer, at: 0)
//when are frames actually rendered (when is video loaded)
player?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: &observerContext)
//loop through video
observer = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
}
}
deinit {
print("deinit")
// remove loadedTimeRanges observer
player?.removeObserver(self, forKeyPath: "currentItem.loadedTimeRanges")
// remove AVPlayerItemDidPlayToEndTime observer
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
// note, `observeValue` should check to see if this is something
// this registered for or whether it should pass it along to `super`
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
// do something
}
首先,您不必在
deinit
中设置nil
属性。根据定义,如果调用了deinit
,这些引用无论如何都将被释放。第二,重新查看控制器未发布,我们必须查看页面视图控制器如何管理其子控制器。我打赌你在那里留了一些参考资料。或者某个地方有一个很强的参考循环。仅供参考,您可以运行应用程序,然后使用“调试内存图”功能查看是什么保持视图控制器的强引用。请参阅@Rob,我找到了导致内存泄漏的函数,并将其编辑到我的答案中。我们可以假定是玩家。此外,我在我的问题中编辑了一个pageviewcontroller函数(viewcontroller之后),因为我真的看不到我在哪里留下了引用。。。如果你能看一看,我将不胜感激!我不想通过大量的代码来猜测是什么保留了对被撤销的视图控制器的强引用,而是在调试器中运行该应用程序,执行您希望解除分配的任何操作,然后使用Xcode的“调试内存图”功能,以精确地确定是什么保留了对应该解除分配的视图控制器的强引用。一旦您看到是什么保持了强引用,解析该引用就会容易得多。可能与我们共享内存图图像。我曾尝试完全追溯它,但由于没有提供追溯(s.“编辑2”),所以无法这样做。我唯一能发现的是它一定是函数。当使用内存图时,我不会从malloc
块开始(因为这些通常是在框架的深处进行的,并且很难与我们的代码关联起来……而且其中可能隐藏着一些我们无法控制的可忽略的泄漏或误报)。首先关注您的对象。例如,下面,我关注的是我没有看到的对象deinit
(即视图控制器),这使问题得到了彻底的缓解。不幸的是,这导致应用程序崩溃。我真的相信这是功能…s.EDIT 2是的,我也看到了崩溃,这是一个单独的问题,即无法删除观察者。请参阅修改后的答案。我将再次检查编辑。(第一条评论与第一版相关)这非常好用!非常感谢您的奉献和帮助!非常感谢!
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
private var observerContext = 0
private weak var observer: NSObjectProtocol?
func setupPlayer() {
let fileName = URL(string: video!)!.lastPathComponent
let fileManager = FileManager.default
let videoFileUrl = try! fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent(fileName)
if fileManager.fileExists(atPath: videoFileUrl.path), player == nil {
let playerItemToBePlayed = AVPlayerItem(url: videoFileUrl)
player = AVPlayer(playerItem: playerItemToBePlayed)
//add sub-layer
playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = view.bounds
controlsContainerView.layer.insertSublayer(playerLayer, at: 0)
//when are frames actually rendered (when is video loaded)
player?.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: &observerContext)
//loop through video
observer = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
}
}
deinit {
print("deinit")
// remove loadedTimeRanges observer
player?.removeObserver(self, forKeyPath: "currentItem.loadedTimeRanges")
// remove AVPlayerItemDidPlayToEndTime observer
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
// note, `observeValue` should check to see if this is something
// this registered for or whether it should pass it along to `super`
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
// do something
}