CPInterfaceController dismissTemplate(animated:, completion:) does not release presented CPTemplate from memory

Description

I present a template to the CPTemplateApplicationSceneDelegate with CPInterfaceController presentTemplate(, animated:, completion:). After this there are two ways to dismiss the template:

  1. In case the presented template has a built-in "Cancel" / "Back" button (like CPVoiceControlTemplate), by pressing the button the template disappears.
  2. You can call CPInterfaceController dismissTemplate(animated:, completion:), which could be connected to some underlying logic. Calling this also makes the template disappear correctly.

The issue can be seen in the Debug Memory Graph under the CarPlay section. In case:

  1. After I dismiss the template by pressing the built-in button the presented template GOES OUT OF MEMORY (disappears from the Debug Memory Graph).
  2. After I dismiss the template with the given function the presented template REMAINS IN MEMORY (stays in the Debug Memory Graph).

This is an unexpected behaviour in my opinion. If I recreate the template before presenting it, a new instance is added to the memory every time. In case of CPVoiceControlTemplate I had animated images in my voiceControlStates. These images take up significant memory and by always remaining in the memory leads to a memory issue.

My expectation is that both the built-in button press and the dismissTemplate(animated:, completion:) function should release the template from the memory.

Reproduction:

A:

  1. Present a CPVoiceControlTemplate with CPInterfaceController.presentTemplate().

  2. Press the built-in "Cancel" button in top leading corner of the template.

  3. Check Debug Memory Graph under CarPlay section.

    ➡️ CPVoiceControlTemplate is NOT PRESENT in memory ✅😎

B:

  1. Present a CPVoiceControlTemplate with CPInterfaceController.presentTemplate().

  2. Call CPInterfaceController.dismissTemplate() after a timeout.

  3. Check Debug Memory Graph under CarPlay section.

    ➡️ CPVoiceControlTemplate is PRESENT in memory ❌😞

Source code

//
//  CarPlaySceneDelegate.swift
//  EVNavigationCarplay
//
//  Created by Ákos Morvai on 2023. 02. 21..
//

import CarPlay
import UIKit

class CarPlaySceneDelegate: NSObject {
    var interfaceController: CPInterfaceController?
    
    private func presentTemplate() {
        let template = CPVoiceControlTemplate(voiceControlStates: [])
        interfaceController?.presentTemplate(template, animated: true, completion: nil)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
            self?.interfaceController?.dismissTemplate(animated: true, completion: nil)
        }
    }
}

extension CarPlaySceneDelegate: CPTemplateApplicationSceneDelegate {
    func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didConnect interfaceController: CPInterfaceController, to window: CPWindow) {
        self.interfaceController = interfaceController
        presentTemplate()
    }
    
    func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didDisconnect interfaceController: CPInterfaceController, from window: CPWindow) {
    }
}

extension CarPlaySceneDelegate: CPTemplateApplicationDashboardSceneDelegate {
    func templateApplicationDashboardScene(_ templateApplicationDashboardScene: CPTemplateApplicationDashboardScene, didConnect dashboardController: CPDashboardController, to window: UIWindow) {
    }
    
    func templateApplicationDashboardScene(_ templateApplicationDashboardScene: CPTemplateApplicationDashboardScene, didDisconnect dashboardController: CPDashboardController, from window: UIWindow) {
    }
}

Debug Memory Graph after programmatic dismiss

Affected Xcode version

I currently use Xcode 15.2 but it happened in earlier versions as well.