Swift framework wrapping a static library

I now spent 2 days trying to create a Swift framework that includes a static (Objective-C) library, so I guess it is time to ask for help here...

My problem: I have a static library created with Objective-C and we need to release a framework that can be used by our customers to use the static library. Creating an Objective-C framework that wraps around the static library is no problem, but as many of our customers use Swift nowadays, it would of course be better if we can create a framework with a cleaned-up Swift interface that wraps around the static library.


What I tried so far (well, the only thing that at least compiled so far): I created a module.modulemap inside of the Objective-C header directory that defines a module so that I can reference it from Swift. But it seems the module is not compiled into the final framework and when I try to use it in a project on another computer (or move it to another directory), I get a "Missing required module" error because the header files are missing on that computer. And I of course do not want to provide the Objective-C headers in the Swift framework.


So my question: Is there a way to do this correctly? One possible solution would be to first create an Objective-C framework and then create a Swift wrapper framework around that Objective-C framework (at least I think that should work), but creating a Swift framework more directly would of course be much cleaner and nicer...


Thanks,

Accepted Reply

Yes, I did learn a few things the hard way. I created a Swift wrapper using XCode 9's possibility to create static Swift frameworks. This way you can include the simulator code inside the frameworks as well. I created a separate static Objective-C framework and a static Swift framework that only wraps around the Objective-C framework. The user then has to add both frameworks to his XCode project but only import the Swift one (so for example add "Swift" to the name of the Swift wrapper to make this clear).


BUT: In the end, we dropped the Swift wrapper again and tried to make the Objective-C framework to translate as good as possible to Swift (using NS_SWIFT_NAME etc.). The reason for this is that Swift doesn't seem to be ready for being used in frameworks. We created the framework using Swift 4.0 and released it to our customers. Then XCode 9.1 with Swift version 4.0.2 came out and that version is not ABI-compatible with Swift 4.0, meaning the customers couldn't compile their projects anymore unless we provided an update. And creating an update each time a new Swift subversion is released is definitely not an option for us (but maybe this will change in the future? Hopefully?).


Creating an Objective-C-only framework that maps as good as possible to Swift doesn't have that problem, but the "as good as possible" part can be a bit problematic. And you have no guarantee that the automatic translation to Swift won't change completely one day (I added NS_SWIFT_NAME to each method and class name because of that, but that is still no guarantee...).

An alternative is to simply release the source-code of the Swift wrapper and let the customers download and include that in their own projects next to the Objective-C framework if they want a nice Swift interface (as for example Facebook has done). But that would add more work for the customers...


As you can see, I spent far too much time trying to create a perfect framework, but in the end I had to find out that it is not possible at the moment. As long as Swift is not ABI-compatible, there will always be problems.

Replies

There's a procedure described here:


https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html


under the heading "Importing Code from Within the Same Framework Target" that is close to what you want. Note that there's no real problem with linking against an Obj-C static library (vs. compiling the Obj-C source code into the target directly), except that you will need to make the header files available to the build process.


That's where things get strange. According to the procedure described, it looks like the framework module will make both the Obj-C and Swift APIs public (to clients of the framework) which may not be what you want. Also, it discusses using an "umbrella" header file, and it's not entirely clear what that means.


What you perhaps really want is a bridging header, as described for an app target, earlier in the same document. Whether that can work for a framework target is not clear, but it's a build setting you could try.


If you don't get anywhere from the documentation, I suggest you ask over on the swift-users list at swift.org. One of the geniuses over there may be able to Explain It All To You™.

Thank you for your answer! I did forget to mention in my question above that I have also tried to make the Objective-C headers public and adding them to the Swift-Framework umbrella header (as described in ""Importing Code from Within the Same Framework Target"). Which does work, but doesn't do what I need, as in the final framework you will be able to call the Objective-C headers directly. What I need is a Swift wrapper that hides the objective-c code and calls that in the background, so that the interface can be optimized for Swift (= hide the Objective-C interface and remap everything)...


And bridging headers cannot be used in frameworks, I'm afraid...


So far I have only found two other solutions that at least work (but none of them is nice):

-Create a separate Objective-C framework, and a Swift framework that wraps around it. This way the user has to import both frameworks (I haven't managed to get an Umbrella Framework that contains both frameworks running so far, seems there are some problems with that), and can see both interfaces, but at least he can choose to ignore the Objective-C one...

-Create a module.modulemap to define a module for the Objective-C code inside of the framework. And then give the user the framework AND the module containing the modulemap and the headers. This way the user then has to add the modulemap path to the "Import Paths" build settings and will also see the Objective-c code separated from the Swift code (but the "Import Paths" step might be a bit weird for the user, so the separate Objective-C framework is probably better...)


I'm still hoping for a better solution, of course....

In the same boat, a few days in. Did you learn anything further?

Yes, I did learn a few things the hard way. I created a Swift wrapper using XCode 9's possibility to create static Swift frameworks. This way you can include the simulator code inside the frameworks as well. I created a separate static Objective-C framework and a static Swift framework that only wraps around the Objective-C framework. The user then has to add both frameworks to his XCode project but only import the Swift one (so for example add "Swift" to the name of the Swift wrapper to make this clear).


BUT: In the end, we dropped the Swift wrapper again and tried to make the Objective-C framework to translate as good as possible to Swift (using NS_SWIFT_NAME etc.). The reason for this is that Swift doesn't seem to be ready for being used in frameworks. We created the framework using Swift 4.0 and released it to our customers. Then XCode 9.1 with Swift version 4.0.2 came out and that version is not ABI-compatible with Swift 4.0, meaning the customers couldn't compile their projects anymore unless we provided an update. And creating an update each time a new Swift subversion is released is definitely not an option for us (but maybe this will change in the future? Hopefully?).


Creating an Objective-C-only framework that maps as good as possible to Swift doesn't have that problem, but the "as good as possible" part can be a bit problematic. And you have no guarantee that the automatic translation to Swift won't change completely one day (I added NS_SWIFT_NAME to each method and class name because of that, but that is still no guarantee...).

An alternative is to simply release the source-code of the Swift wrapper and let the customers download and include that in their own projects next to the Objective-C framework if they want a nice Swift interface (as for example Facebook has done). But that would add more work for the customers...


As you can see, I spent far too much time trying to create a perfect framework, but in the end I had to find out that it is not possible at the moment. As long as Swift is not ABI-compatible, there will always be problems.