How to import Swift package into Objective-C using Xcode

Hello,

Hope someone can help as I've been pulling my hair out over this.

Say I have a pure Swift language SPM package library called "TestPackage" that exposes some of it's API to Objective-C:

@objc
class TestClass: NSObject {}

How do I then consume this in a mixed-language Xcode app target from Objective-C .h files like:

#import <Foundation/Foundation.h>

@import TestPackage;

NS_ASSUME_NONNULL_BEGIN

@interface SomeObject : NSObject

- (void)setObject:(TestClass *)testObj;

@end

NS_ASSUME_NONNULL_END

I get "Module 'TestPackage' not found" error and "Expected an type" error on - (void)setObject:(TestClass *)testObj;

An example Xcode projects can be found here:

Which uses TestPackage SPM package:

For my specific use-case, I also need to reference the Swift type in the Objective-C header and then use the type when calling the Objective-C setObject method.

3 Likes

Does this help?

Unfortunately not, though that OP does appear to have the same issue as me and I've seen threads where it's apparently fixed in Xcode 11.4 but I don't see any change of behaviour and I'm using Xcode 12.3. My test project attached is super simple and it just doesn't work.

@Aciid mentions a fix on another post in an earlier Swift version but I think that only applies when SPM is performing the build operations. Seems Xcode likes to do it's own thing and isn't generating the headers.

I can't believe I'm the only developer in the world who needs to use SPM libraries in mixed Swift/Objective-C codebases where the symbols need to used in Objective-C and Swift application code?

2 Likes

If it helps, I tried to make a very simple Swift package and use it in an Obj-C project. I found that the classes had to be marked as public.

I could instantiate the class in Obj-C and call its functions, but alas I could not work out how to access the class's properties.

import Foundation

@objcMembers public class SwiftClass : NSObject {
    @objc let text = "Foo"
    
    public func sayHello() {
        print(text)
    }
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
    
    SwiftClass *swiftClass = [[SwiftClass alloc] init];
    NSLog(@"swiftClass.txt: %@", swiftClass.text); 
     // Error: Property 'text' not found on object of type 'SwiftClass *'
    [swiftClass sayHello];
}

Properties default to internal, not just methods. Have you tried making text public?

1 Like

That works. Thanks. I could have sworn that I had tried that....

Just to be clear as shown in my example projects, I'm unable to import the Swift module in the Objective-C .h files. I just get module not found. My TestClass is public, though the initialiser isn't though this shouldn't prevent the module from being created.

Apologies, I read your post too quickly and made some assumptions.

1 - The Obj-c .h file error: You don't @import swift modules into Obj-C headers, you forward declare classes instead: e.g. @class TestClass;

2 - The 'expected a type' error in ViewController.swift appears to go away if the @objc annotation is on the same line as the Class declaration. e.g. as in my post above

import Foundation

@objcMembers public class TestClass : NSObject {
    @objc let text = "Foo"
    
    public func sayHello() {
        print(text)
    }
}

After that there's an error compiling because you are calling setObject on TestClass, which is actually a method on your SomeObject class, not TestClass.

Hmm, having said that... When I use @class in SomeObject.h the setObject: method appears not to be exposed to Swift... Other methods and properties do though... e.g. isEnabled and a simple test method which I added.

There seems to be a way to work around it by using id instead of TestClass in the method.
e.g.

  • ( void )setObject2:( id )testObj;

Or this page may help:

https://cjwirth.com/tech/circular-references-swift-objc

Yep that's exactly the issue I've had, the symbols are converted to Any when using forward class declarations but I can't import the module either to resolve the symbol when removing the @class but I do have circular dependencies. Tricky stuff, will take a look at that link, thanks!

The better solution is simply don't use Swift Package Manager as a replacement for Xcode project files. SPM wasn't designed to work with modules declared in Xcode project files and Apple provides neither support/workarounds, nor do they seem interested in writing tests to make sure Swift Packages can fully replace all the use-cases of Xcode project files, especially in mixed-source apps...

Hi, I have made one Swift package and enable ObjectiveC to call its functions, classes.
You can see the project at this address

Download the code from Github and open the "Package.swift" file to see the structure of the project.
How to call the swift classes/function from this package is in the Readme file in Github.
This is not simple but not too complicate package, do the work of Ed25519 and Secp256k1 encryption in Swift, provide its functions to ObjectiveC to call.

It is certainly possible to use Swift packages in your Objective-C code—I am doing this myself.

To avoid circular references between your Objective-C files and your Swift code, you may need to put your @import TestPackage; (you do have the correct syntax) at the top of your .m file and then use a forward class declaration in your .h file for those Swift classes within the package that are referenced in methods in your .h file. Others above mentioned using a forward class declaration but I didn't see the specific point that you still do use @import, just in your .m file.

And of course, you can only access types, properties and methods on those Swift classes that are both public and do not rely on any Swift-specific features, like generics or Swift protocols. There's usually wrapper methods and so on that can be used to access that code of course, such as writing a Swift extension to your Objective-C class that provides a method to bridge the two.

Did any experiments here ever work out? Here's a similar post with more details on my situation Swift Can't See ObjC Methods that Include Package Symbols - #5 by gestrich

1 Like