由于AVPlayer和观察者未初始化失败而导致的内存泄漏

莫里兹

在10/2020中进行编辑:先前的标题为“未在UIPageViewController的UIViewController中调用Deinitializer”

我想在我的UIViewController(是UIPageViewController的一部分)中使用以下反初始化程序来删除myplayerLayer并将其设置为playernil以便内存不会过载(因为deinit应该始终调用UIViewController,因为这样就不再需要了) ):

deinit {
        
    self.player = nil
    self.playerLayer.removeFromSuperlayer()
    print("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, viewControllerAfter viewController: UIViewController) -> UIViewController? 
{
    
    let currentIndexString  = (viewController as! MyViewController).index
    let currentIndex        = indec.index(of: currentIndexString!)
    
    //set if so that next page
    if currentIndex! < indec.count - 1 {
    
        //template
        let myViewController = MyViewController()
    
        //enter data into template
        myViewController.index            = self.indec[currentIndex! + 1]
    
        //return template with data
        return myViewController
        
    }
    
    return nil
    
}

编辑2:

如您所见,没有回溯,请注意该malloc的大小(右上)和类似的大malloc的大小(左下)。

没有回溯

如果我们在“调试内存图”中查看对象图,则可以看到:

在此处输入图片说明

我们可以看到视图控制器是由闭包(中间路径)捕获的。我们还可以看到观察者保持着强烈的参考(那条底部的道路)。

Because I turned on "Malloc stack" feature (shown in https://stackoverflow.com/a/30993476/1271826), I could click on the "Closure Captures" and can see the stack trace in the right panel:

在此处输入图片说明

(Forgive me that that memory graph is slightly different than the first screen snapshot because I fixed the other memory issue, the observer, as discussed at the end of this answer.)

Anyway, if I click on the highest entry in the stack trace that's in black (i.e. the last bit of my own code in that stack trace), it takes us directly to the offending code:

在此处输入图片说明

That draws our attention to your original code:

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player?.currentItem, queue: nil, using: { (_) in
    DispatchQueue.main.async {
        self.player?.seek(to: kCMTimeZero)
        self.player?.play()
    }
})

The closure is keeping strong reference to self. You can correct that with:

NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
    self?.player?.seek(to: kCMTimeZero)
    self?.player?.play()
}

Note, the [weak self] capture list in the closure.


顺便说一句,而你并不需要nilplayerdeinit,你就需要删除观察员。我还将context为您的观察者设置一个,以便您observerValue(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
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

阵列重新初始化会导致内存泄漏吗?

LiveData 在避免内存泄漏方面优于观察者对象

由于内核未封闭而导致的内存泄漏

CPP中的Valgrind和内存泄漏:“有条件的跳转或移动取决于未初始化的值”

限制IObservable导致未调用观察者

使用函数初始化静态向量会导致内存泄漏吗?

内存访问与结构和观察者模式冲突

添加观察者时 AVPlayer 崩溃

由于org.springframework.core.ResolvableTypeProvider导致上下文初始化失败

Java,线程“主”中的异常java.lang.NullPointerException由于未初始化对象而导致

使用未初始化的内存

ViewController取消初始化后,UIImageView上的内存泄漏

在TensorFlow中多次初始化变量会泄漏内存

hboehm 垃圾收集器未初始化值错误和泄漏

Java观察者导致循环继承

由于未捕获的异常'NSRangeException'而终止应用程序,原因:'无法删除观察者-iOS

观察者未在初始化时触发,但可在装入时工作

由于 _startDate 导致 Flutter 中的延迟初始化错误

修复由于多次初始化Firebase而导致的Firebase错误

由于Firebase初始化,导致App Delegate崩溃

观察者未更新角度范围

MutableLiveData的观察者未触发

Aurelia观察者未触发阵列

iOS NSNotificationCenter观察者未删除

未调用 iOS 前台观察者

未初始化和内存错误(字符串和指针)

由于Netty中的ByteBuffers而导致的内存泄漏

Android:由于成员变量导致内存泄漏?

由于 Unity 中的 MapBox 导致内存泄漏