swiftswiftuipushkit

PushKit delegate methods not being called after putting them in a separate manager class


When I add the PushKit delegates in the App Delegate, it works perfectly. Here's the code:

class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions:
                     [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
        
        self.voipRegistration()
        
        return true
    }
    
    // Register for VoIP notifications
        func voipRegistration() {
            // Create a push registry object
            let mainQueue = DispatchQueue.main
            let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
            voipRegistry.delegate = self
            voipRegistry.desiredPushTypes = [.voIP]
        }
}


// MARK: - PKPushRegistryDelegate
extension AppDelegate : PKPushRegistryDelegate {

    // Handle updated push credentials
    func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
        let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined()
        print("pushRegistry -> deviceToken :\(deviceToken)")
    }

    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
        print("pushRegistry:didInvalidatePushTokenForType:")
    }

    // Handle incoming pushes
    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        // Show call screen
        if type == .voIP {
            print("VoIP")
        } else {
            print("Nope")
        }

        if UIApplication.shared.applicationState == UIApplication.State.active {
            print("Active")

        } else {

            let config = CallKit.CXProviderConfiguration()
            config.supportsVideo = true
            let provider = CXProvider(configuration: config)

            let testCallKit = TestCallKitFile()
            testCallKit.recieveACall(provider: provider)
        }
    }
}

This code works because a token is being printed from the didUpdate credentials method. But when I put the delegate methods in a separate class, they are not being called (I know that because the token is not being printed). Here's the code:

VoIPNotificationManager class

class VoIPNotificationManager: NSObject, PKPushRegistryDelegate {
    
    func registerForVoIPNotifications() {
        let mainQueue = DispatchQueue.main
        let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
        voipRegistry.delegate = self
        voipRegistry.desiredPushTypes = [.voIP]
    }
    
    // Handle updated push credentials
    func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
        let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined()
        print("pushRegistry -> deviceToken :\(deviceToken)")
    }
    
    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
        print("pushRegistry:didInvalidatePushTokenForType:")
    }
    
    // Handle incoming pushes
    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        // Show call screen
        if type == .voIP {
            print("VoIP")
        } else {
            print("Nope")
        }
        
        if UIApplication.shared.applicationState == UIApplication.State.active {
            print("Active")
            
        } else {
            
            let config = CallKit.CXProviderConfiguration()
            config.supportsVideo = true
            let provider = CXProvider(configuration: config)
            
            let testCallKit = TestCallKitFile()
            testCallKit.recieveACall(provider: provider)
        }
    }
}

And I am calling this in the appDelegate's didFinishLaunchingWithOptions method like so:

// Register for voip notifs
let voipNotificationManager = VoIPNotificationManager()
voipNotificationManager.registerForVoIPNotifications()

Also, this is a SwiftUI app.


Solution

  • It looks like you don't store a reference to your voipNotificationManager outside of the didFinishLaunchingWithOptions method. Am I right? If so, the manager just gets deallocated after the didFinishLaunchingWithOptions finishes. As there are no strong references left.

    Here is how memory management works in swift link

    What I would suggest you do is make your voipNotificationManager a property on the app delegate like so

    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        let voipNotificationManager = VoIPNotificationManager()
    
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions:
                         [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            FirebaseApp.configure()
            
            voipNotificationManager.registerForVoIPNotifications()
    
            return true
        }
    
       ...
    
    }