Using a Swift Package in a mixed Swift and Objective-C project

Hello,

I apologize in advance if this question has been asked before (I searched the forum but did not find anything).

On a brand new project, I created swift sources and Objective-C sources and made sure they worked together. I then proceeded to create a new local Swift Package in Xcode 11 beta 4 with a single Swift file that defined a NSObject subclass with a string inside.
When I tried to use the package class from the project Swift code, it went fine, as expected, after importing the module. I then tried using the package class from the project Objective-C code and no matter what, I could not make it work.

Is this expected behavior? If so, why? And why doesn't a '#import "Package-Swift.h"' or 'import <Package/Package-Swift.h>' in the .m file where I want to use the package code solve the problem?

Thank you for the clarification.

4 Likes

This is a known issue. You can't import a Swift package target into an Objective-C target right now.

1 Like

Thank you for the quick response.
Is there a Jira ticket that we can follow regarding a future implementation of this feature? Is there a timeframe for it?

3 Likes

Clearly "Swift" packages are meant to be used with Swift code and all new development should be done in Swift but this makes it really difficult to add features into existing projects where the code path runs through ObjC code. I'm just curious as to why this doesn't "just work"; I know nothing about the internals of SPM.

Are there any details about this or a JIRA task associated with it? We're looking to migrate to using packages sometime in the future and we're trying to plan a path forward, not being able to import Swift targets into ObjC targets in a large roadblock to that.

This required some work in the build system. At a higher level, SwiftPM needed to ask the Swift compiler to emit an ObjC compatibility header file which can be used by the ObjC targets.

I landed support for this in master couple of days ago.

10 Likes

That's amazing! I guess two follow up questions:
Any idea on availability within Xcode? (sort of apple-specific question I guess)
Also the PR mentions it being only for macOS is there a purpose for this? Our case would be iOS/iPadOS.

Edit: I just realized that comment may be pertaining to the build agent, as in, only SPM implementations built using macOS would see this functionality since it's the only place ObjC is accessible. Is that correct to say?

2 Likes

I'm having the same issue.

It looks like it has already been fixed but isn't available yet (likely at least until after Nov 5 when 5.2 is cut). Whatever Xcode version first adopts 5.2 I imagine would have this functionality.

The real question is is it possible to download the latest toolkit and test this functionality within Xcode now?

2 Likes

Just fyi, this issue is resolved in Xcode 11.4.

5 Likes

If the SwiftPM is responsible for generating the header file, how do you reference it from an Xcode project? Is this done automatically?

I ask, because I see a number of examples online where a Run Script step is required to get ahold of the the header files, and I'm wondering if this is available when using SwiftPM.

It should just be implicitly through the module import. The reason those kind of scripts exist I believe is because in the past those compatibility header files weren't generated. Now if I have Swift module FooSwift and import it into my .m ObjC file in FooObjC like so @import FooSwift; it should see everything made available to ObjC from FooSwift. Someone else can correct me if I'm wrong I've just done a few preliminary tests with a mix of library types.

2 Likes

I had my 'C' head on, and I was using #import instead of @import.

Thanks for the suggestion!

Update: on further inspection, it seems that I was mislead by the documentation, which recommends using #import for in project and framework Swift code. See Apple Developer Documentation . I guess they need a special section for Swift Packages

3 Likes

Does it work in Xcode11? Because I have some problem. I have class defined in Package

@objc public class ObjCCountry: NSObject {
    @objc public let id: Int
    @objc public let title: String
    @objc public func getTitle() -> String { title }
    @objc public func printTitle() { 
        print("title: \(title)")
    }
    public init(id: Int, title: String) {
        self.id = id
        self.title = title
    }
}

And no one of these properties and methods I could not see in .mm file

  #import "SearchCountries-Swift.h"
     ...
  ObjCCountry *z = [gate getCountry];
  NSLog(@"run, z: %@", z.title); // Property 'title' cannot be found in forward class object 'ObjCCountry'

While Option+Click on ObjCCountry and Jump to Definition sends me to the source code in the package

1 Like

Sorry to revive a dead thread but I need to correct what I said previously...

It should just be implicitly through the module import. The reason those kind of scripts exist I believe is because in the past those compatibility header files weren't generated. Now if I have Swift module FooSwift and import it into my .m ObjC file in FooObjC like so @import FooSwift; it should see everything made available to ObjC from FooSwift. Someone else can correct me if I'm wrong I've just done a few preliminary tests with a mix of library types.

This ONLY works if the library consuming the Swift (or ObjC) package has the package product linked EXPLICITLY in the consuming library's Link Binary With Libraries phase. This is a huge issue because then now if you are like us and have many many libraries within your application then they would all have to link the library directly which only works if you make the swift package product's type .dynamic as to avoid duplicate symbols.

Then as you continue to convert things to SPM you have more and more dynamically linked things making app launch times horrendous.

If anyone has any insight on how to import a Swift (ObjC) package into an ObjC static library that is a child project of the main app project without explicitly linking it to the static library let me know...

TLDR: Can't use @import SomePackageLibrary in ObjC code in static library without explicit linking, and can't import SomeObjCPackageLibrary in swift code in static library without explicit linking.

1 Like

The problem is, you shouldn't convert things into Swift packages. It is not suitable as a replacement for Xcode project files to declare local modules in many cases—this was simply not a use-case that SPM was designed to handle, and Apple has put little to no priority on making SPM work for this use case.

I'd love to be proven wrong, but literally every single time I've used a Swift Package instead of an Xcode project file for a local target, I've later come to seriously regret it.

AND you can't @import a Swift package target in an ObjC header if that header is part of a module that's defined using an Xcode project file—making SPM worse than useless as a replacement for Xcode project files.

You can still import your Swift module from ObjC without having to add it to the Link Binary With Libraries by adding -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/FooSwift.modulemap to the Other C Flags of your ObjC target. You still need to have FooSwift in the target's dependencies to make sure to module map is generated.

Hey thanks for the reply and welcome!

While this does make the import and building it work, unfortunately adding FooSwift as a dependency requires it live within the same project file as the ObjC target that is dependent on it which in our case is unfeasible (we have 80ish targets all in their own sub-projects of the main app target project).

Perhaps adding the package as a dependency to the main app target will work. I'll play around with it a bit and see. I've never been able to get it to import without linking though so that's new. Thanks!

1 Like

If you can figure out where those Swift packages are generating their modulemaps, you should be able to replace GENERATED_MODULEMAP_DIR with some other path.