Segue/unwind repeatedly causing memory to keep increasing when video is loaded

134 Views Asked by At

I am writing a very simply app that has two View Controllers - FirstVC and SecondVC. On the FirstVC, I load a video (apples.mp4) which loops forever in the background (of my view). The video loading is done by the displayBackgroundVideo() function which is executed from the FirstVC's viewWillAppear() function.

Here is the code for FirstVC:

override func viewWillAppear(_ animated: Bool) {
    displayBackgroundVideo()
}

func displayBackgroundVideo() {  
    var videoPlayer : AVPlayer?
    var videoPlayerLayer : AVPlayerLayer?

    let bundlePath = Bundle.main.path(forResource: "apples", ofType: "mp4")
    guard bundlePath != nil else { return }

    let url = URL(fileURLWithPath: bundlePath!)
    let item = AVPlayerItem(url: url)

    videoPlayer = AVPlayer(playerItem: item)

    videoPlayerLayer = AVPlayerLayer(player: videoPlayer!)

    videoPlayerLayer?.frame = CGRect(x: -self.view.frame.size.width*2, y: 0, width: self.view.frame.size.width*5, height: self.view.frame.size.height)

    view.layer.insertSublayer(videoPlayerLayer!, at: 0)

    videoPlayer?.playImmediately(atRate: 0.6)

    // Loop video forever
    NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: videoPlayer?.currentItem, queue: .main)
    { _ in
        videoPlayer?.seek(to: CMTime.zero)
        videoPlayer?.playImmediately(atRate: 0.6)
    }
}

I also have a button on the FirstVC which if you press it, will segue to the SecondVC. Similarly, in the SecondVC, I have a simple button which will unwind back to the FirstVC. There is nothing else in the SecondVC.

PROBLEM: If I keep going back and forth between the FirstVC -> SecondVC -> FirstVC -> SecondVC ->... and so on, I notice that the memory keeps increasing! However, if I comment out the displayBackgroundVideo() function in the FirstVC (i.e. do not load the video), then I get the expected behavior which is going back-and-forth will increase and decrease the memory (since we are segueing and unwinding back successfully).

So why is it that my memory is blowing up (increasing) if I display the video? How can I solve this problem?

1

There are 1 best solutions below

2
On BEST ANSWER

I believe you have created a memory leak there.

addObserver(forName:object:queue:using:) actually returns an opaque object to act as the observer and by keeping that reference you can use it in the deinit to actually remove that observer when releasing the VC.

With this in mind, I'd suggest to update your code to move the setup in viewDidLoad instead of viewWillAppear and as well to add the deinit to remove the observer:

class FirstVC: UIViewController {

    private var backgroundVideoPlayerObj: NSObjectProtocol?
    
    override func viewDidLoad(_ animated: Bool) {
        super.viewDidLoad(animated)
        displayBackgroundVideo()
    }

    deinit {            
        if let backgroundVideoPlayerObj = backgroundVideoPlayerObj {
            NotificationCenter.default.removeObserver(backgroundVideoPlayerObj, name: .AVPlayerItemDidPlayToEndTime, object: nil)
        }
    }

    func displayBackgroundVideo() { 
        var videoPlayer : AVPlayer?
        var videoPlayerLayer : AVPlayerLayer?

        ...

        backgroundVideoPlayerObj = NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: videoPlayer?.currentItem, queue: .main) { _ in
            videoPlayer?.seek(to: CMTime.zero)
            videoPlayer?.playImmediately(atRate: 0.6)
        }
    }

}