objective-cswiftnsobjectbridging-headerlanguage-interoperability

How to make Swift class conform to Objective-C protocol, without exposing it to Objective-C?


I have a Swift class called LoginCoordinator that performs a "Sign In with Apple" request. In order respond to callbacks it conforms to the ASAuthorizationControllerDelegate. Because of this requirement, I also need to make this class a subclass of NSObject:

class LoginCoordinator: NSObject { ... }
extension LoginCoordinator: ASAuthorizationControllerDelegate { ... }

This class will not be used by any Objective-C classes at all. It is intended to be consumed by other Swift classes (i.e. LoginViewController) which will indeed be used by Objective-C classes

The problem is as follows. I eventually import the top-level Swift classes into Objective-C by doing:

#import "MyProject-Swift.h"

This causes a compile-time error in the generated MyProject-Swift.h file that says:

Cannot find protocol declaration for 'ASAuthorizationControllerDelegate'; did you mean 'UINavigationControllerDelegate'?

This error appears on the line for the generated Objective-C category that corresponds to the extension that I wrote above in Swift, where I make LoginCoordinator conform to ASAuthorizationControllerDelegate.

If, before importing MyProject-Swift.h, I also import <AuthenticationServices/AuthenticationServices.h>, then the error goes away. But I do not want to import this everywhere I import Swift.

If I understand correctly, what is going on is this: when I import my Swift files into Objective-C, it imports all the generated headers in MyProject-Swift.h. But one of those category headers (LoginCoordinator) references a protocol (ASAuthorizationControllerDelegate) that has not been imported, which raises an error.

I noticed that MyProject-Swift.h only began to generate code for LoginCoordinator once I made it a subclass of NSObject. Removing the subclassing removes the generated code, but of course it then cannot conform to the protocol.

Is there a way to make a Swift class a subclass of NSObject and conform to an Objective-C protocol, without exposing it when importing Swift files in Objective-C?


Solution

  • You can create a private object that actually conforms ASAuthorizationControllerDelegate, and forward message to LoginCoordinator. It's ugly, but can solve the quetion.

    class LoginCoordinator {
        private lazy var authDelegate: LoginCordinatorASAuthorizationControllerDelegate {
            LoginCordinatorASAuthorizationControllerDelegate(coordinator: self)
        }
    }
    
    //MARK: functions from ASAuthorizationControllerDelegate, but not conforms it
    extension LoginCoordinator {
        private func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization....)
    }
    
    private class LoginCordinatorASAuthorizationControllerDelegate: NSObject, ASAuthorizationControllerDelegate {
        weak var coordinator: LoginCoordinator!
        init(coordinator: LoginCoordinator)
    }
    

    You can also make your LoginCoordinator a nested class which is not able to expose to Objective-C.

    enum Login {
        class Coordinator: NSObject, ASAuthorizationControllerDelegate {...}
    }