Swift framework independent of OS

I am developing a framework for Swift that is independent of the OS flavor it should work on iOS, macOS etc. The framework only contains an API for calculations, and therefore only imports Foundation. It doesn't deal with views or anything and UIKit or AppKit are not used.

When I started the project a while ago, I was following some tutorial and ended up with two targets: MyFramework_macOS and MyFramework_iOS. But in hindsight, I'd rather have only one target: MyFramework.

Is this possible, or do I need to maintain targets for each OS?

Yes and no. Binaries are entirely different. MacOS needs the X86_64 architecture, iOS, various flavors of arm64. You can make a fat binary using lipo during the final build steps, or you can build a .xcframework that was discussed at WWDC. Those might be options for a multi-binary framework.

Oh yes - I forgot about the different architecture requirements. I'll look into your suggestions. Cheers.

Do you actually need a framework? Unless your source is top secret, what you are doing sounds like a good fit for a package instead, which would not need to care about architectures or bundling details.

Yes, I have looked into that. But alas, my Mac is too old for the newest Xcode version, which as I understand is required to work with SPM. I am on High Sierra and Xcode 10 dot something (not at my Mac right now).

You can still use packages, you just need to do it indirectly by running swift package generate-xcodeproj, and then dragging that project into the same workspace as whatever client project. Such a generated project supports all Apple targets automatically.

1 Like

Is dragging the xcodeproj into the client project the only option with Xcode 10? Or can I also use a Package.swift manifest file in the client project to import the package?

Using package manifests within Xcode came with Xcode 11

If the client itself can be converted to a package, then you don’t need Xcode at all. Otherwise, as @jonprescott said, only Xcode 11 and up can read manifests directly.

You can make a fat binary using lipo during the final build steps

To be clear, this is commonly done but it’s not officially supported. All the architectures in a universal binary must be for the same platform. If you mix platforms you will eventually bump into some corner case that fails.

If you want to target different platforms, use an XCFramework.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

With Xcode 10? (That was the OP’s constraint.)

So I was able to create a Package using the swift init package and swift package generate-xcodeproj commands, added the sources from the old framework, and made it compile and test. The product is still called MyFramework.framework, which I then added to the "Link Binary with Libraries" section under "Build Phases" in the client project. And again, it compiles.

Does this sound correct?

Yes.

Note that while all the source and project files are shared, the resulting instance of the framework that actually gets built and embedded is distinct for each target platform.

Excellent! Thanks all for your answers.

With Xcode 10?

No. Sadly, prior to Xcode 11 there is no supported way to create a multi-platform library.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

Will people who are using Xcode 11 be able to import and use this package by adding the git url to the manifest file of the client project?

They will be able to depend on it directly, yes. (Though that is done through the UI; an Xcode project has no user‐editable manifest of its own.)

Sorry, I meant adding the git url to the Package.swift file of the client project.

Yes, any client uses a Package.swift of its own can import and use your package by simply adding your URL to their Package.swift. (Provided your URL is accessible to them, of course.)

When I tried using 1 target for all platforms like half a year ago, my example target containing a watchOS and an iOS app, both importing the framework, couldn't do clean build, I had to build the framework for watch first, then hit build for the example target.
There is a setting, I think it's called Product Name, which defines what the person has to write after the import keyword, you should set it to the same value for all your different platform targets.