How do I setup a Swift Package with Swift and Objective-C code to have just one module import statement for the user of the package?

I have a mixed Swift and Objective-C app wich is build on mixed Swift and Objective-C frameworks. I'd like to convert the frameworks now step by step to Swift Packages.

My current problem looks like this:

Let's say I have a Swift Package called AppCore with a Swift and an Objective-C target building a library product. The AppCore target contains Swift code and the AppCoreObjC target contains Objective-C code.

let package = Package(

    name: "AppCore",

    products: [
        .library(
            name: "AppCore",
            targets: ["AppCore"]),
    ],

    dependencies: [
    ],

    targets: [
        .target(
            name: "AppCoreObjC",
            dependencies: []),
        .target(
            name: "AppCore",
            dependencies: ["AppCoreObjC"]),

        .testTarget(
            name: "AppCoreTests",
            dependencies: ["AppCore"]),
        .testTarget(
            name: "AppCoreObjCTests",
            dependencies: ["AppCore"]),
    ]

)

Let's say under the AppCore target I have a Swift class:

public class Foo {
}

And under the AppCoreObjC target I have an Objective-C class:

@interface Bar 
@end

In my app I'd now like to have Swift code using the Swift and Objective-C part of AppCore like this:

import AppCore // <---

class MyAppCorePackageUser {

func example () {
    let f = Foo()
    let b = Bar()
}

}

and the same for the Objective-C code of the app, like this:

@import AppCore; // <---

@interface MyAppCorePackageUser: NSObject
@end

@implementation MyAppCorePackageUser

- (void) example {
    Foo* foo = [[Foo alloc] init];
    Bar* bar = [[Bar alloc] init];
}

@end

The important point: For the user of AppCore there should be just one module name and therefor one import AppCore statement and now need to also import AppCoreObjC!

For the Swift part of the AppCore package I can make this possible by adding a file e.g. AppCore.swift holding this line:

@_exported import AppCoreObjC

Therefor the Swift part of my app works liked outlined above.

However, I didn't find a way to make the same thing work under Objective-C. So instead of the ideal code above the actual Objective-C code looks like this:

@import AppCore;
@import AppCoreObjC; // <---

@interface MyAppCorePackageUser: NSObject
@end

@implementation MyAppCorePackageUser

- (void) test {
    Foo* foo = [[Foo alloc] init];
    Bar* bar = [[Bar alloc] init];
}

@end

I need to add an extra @import AppCoreObjC; statement to make Bar available and the code compile.

My question: Is there a way to have a mixed Swift and Objective-C Swift Package AppCore making it's whole mixed API available under one module name: import AppCore (Swift world) or @import AppCore; (Objective-C world)? Can you point me to any sample code?

1 Like

Why can't you just put both the Swift and Objectice-C code in a single target? You can still have separate sub-folders.

SPM does not allow mixed-language targets.

@tgoyne: Are you referring to the suggestion of @Peter-Schorn or my question?

You can AFAIK use the @_exported import AppCoreObjC where you import it, does that work mayhapse?

@BastianInuk: Indeed, for the Swift side of code this works. However, my problem is on the Objective-C side of things.

1 Like

@zykloid: I was faced with a similar issue and came to the following conclusions:

  • using an underscore prefix for the Objective-C target is a good way to hide it in imports so it won't be presented
  • if you subclass the Objective-C class in your Swift target code, the Objective-C base class will be available in your project for Objective-C files with the simple @import AppCore. If you look at the build directory you can see in the AppCore-Swift.h file that now it is properly included because of the subclassing, while just using @_exported import AppCoreObjC didn't have the same effect

I'm still looking into ways to trigger it without any workarounds, but at least this gives you a hint on what is missing for Objective-C.