Extending a swift class from a different target

I have an iOS project with the following targets

SwiftExtensions (AppTarget) -> depends on Experience
Experience (StaticLibrary) -> depends on Lifecycle
Lifecycle (StaticLibrary)

I have defined the SceneDelegate in Lifecycle library:

public class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    // scene(_:willConnectTo:options:) is implemented in Experience

    // scene(_:openURLContexts:) is implemented in Experience

    // Other methods such as sceneWillEnterForeground(_:), sceneDidBecomeActive(_:) etc.
}

As shown above, scene(_:willConnectTo:options:) and scene(_:openURLContexts:) are not defined here.

In the Experience library, SceneDelegate is extended:

extension SceneDelegate {
    
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        NSLog("[Experience]: SceneDelegate.scene(_:willConnectTo:options:)")
        
        if (connectionOptions.urlContexts.isEmpty) {
            NSLog("[Experience]: Not launched using file!")
        } else {
            
            NSLog("[Experience]: Launched using file!")
            
            let urlContexts: Set<UIOpenURLContext> = connectionOptions.urlContexts
            
            for (index, urlContext) in urlContexts.enumerated() {
                NSLog(String(format: "[Experience]: url[%d] = %@ ", index, urlContext.url.path()))
            }
        }
    }
    
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        
        NSLog("[Experience]: SceneDelegate.scene(_:openURLContexts:)")
        
        for (index, urlContext) in URLContexts.enumerated() {
            NSLog(String(format: "[Experience]: url[%d] = %@ ", index, urlContext.url.path()))
        }
    }
}

Now, when I tap the app icon, scene(_:willConnectTo:options:) is not invoked. When I tap an associated file type, scene(_:willConnectTo:options:) is not invoked, again. If app is running in background, and I foreground the app by tapping an associated file type, scene(_:openURLContexts:) is not invoked.

Basically, when I define these two methods outside the target, despite with public access modifier, iOS doesn't invoke my delegate methods defined in the other library. My understanding was, Extensions can be used to break up the class. What is missing here?

I think the reason your case doesn't work is because the extension is not visible to the target that declares the conformance. Protocol methods are dynamically dispatched, but the actual mapping between methods and their implementations is static. This thread describes the same problem: Method Dispatch for Methods in Protocol Extensions in Separate Modules - #4 by Joe_Groff

1 Like

That helps, thank you.

@rockbruno As mentioned in the attached thread, I tried to have a SceneDelegate in ObjC and break it up using the concept of ObjC categories.

The hierarchy looks like this:

SwiftExtensions (AppTarget) -> depends on Experience
Experience (StaticLibrary) -> depends on Lifecycle
    Extension of the original SceneDelegateObjCpp containing scene:willConnectTo:options: and scene:openURLContexts:
Lifecycle (StaticLIbrary)
    Original SceneDelegateObjCpp

When I launch the app, scene:willConnectToOptions: is not invoked. Same for scene:openURLContexts :frowning: .