我希望我的UIViewController(它是UIPageViewController的一部分)中的以下deinitializer删除我的playerLayer
并将player
设置为nil
以便内存为没有重载(因为当UIViewController不再需要时,应始终调用deinit
):
deinit {
self.player = nil
self.playerLayer.removeFromSuperlayer()
print("deinit")
}
要检查deinit
是否曾被执行过,我添加了打印件,发现它永远不会被调用。有人能解释为什么不叫它?你有什么建议我去做我想做的事情?
编辑:
在Rob建议的instructions in this question之后(在评论中),我发现以下函数导致内存泄漏。如果可以在文档目录中找到文件,则该函数应该设置播放器。
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(右上角)和类似大型mallocs(左下角)的大小。
答案 0 :(得分:2)
如果我们在&#34; Debug Memory Graph&#34;中查看对象图,我们可以看到:
我们可以看到视图控制器被闭包(中间路径)捕获。我们还可以看到观察者正在保持强烈的参考(底部路径)。
因为我打开了&#34; Malloc stack&#34;功能(如https://stackoverflow.com/a/30993476/1271826所示),我可以点击&#34;关闭捕获&#34;并且可以在右侧面板中看到堆栈跟踪:
(请原谅我那个内存图与第一个屏幕快照略有不同,因为我修复了另一个内存问题,即观察者,正如本答案末尾所讨论的那样。)
无论如何,如果我点击堆栈跟踪中黑色的最高条目(即该堆栈跟踪中我自己代码的最后一位),它会直接将我们带到违规代码:
这引起我们对原始代码的注意:
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]
捕获列表。
顺便说一句,虽然您不需要在nil
中player
deinit
,但您需要删除观察员。我还为您的观察者设置了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
}