Swift Package Manager iOS platform version

With Swift 5, the Package Manager has been updated to allow platform specifications for packages. All the examples I have seen use a predefined enum or static value (I am unsure which) to define the platforms, like this:

platforms: [.iOS(.v12)]

If you are targeting Swift 5 exclusively and don’t want to support Swift 4.x, this is limiting because there is no defined value for iOS 12.2, which is required if you are not going to ship the Swift binaries in your library. All is not lost. If you look at the source the version can be specified as any 2 or 3 dot separated string. So you can do this:

platforms: [.iOS("12.2")]

Now you can build exclusively for iOS 12.2.

File paths, URL and Swift JSONDecoder

I have been using a JSON file for storing configuration parameters for a command line tool I am writing. Some of these configuration items are paths to various directories like this:

{ "codeRepo" : "/Users/Blob/myRepo" }

And I have a Decodable Swift struct that can by instantiated by this configuration file:

struct Config: Decodable {
    let codeRepo: URL
}

When your JSONDecoder deserializes the JSON object into the URL it will not create a File URL. This is important for some other classes in Foundation, such as Process.

Process requires it’s currentDirectoryURL and executableURL items to be set to file URLs (e.g. created with the URL(fileURLWithPath:…) initializers) otherwise it will throw the following Objective-C exception:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSConcreteTask setExecutableURL:]: non-file URL argument'

This tool is for macOS, but now I am genuinely curious what would happen on Linux since there is no Objective-C backing for Process.

Objective-C and Swift mixed modules

Say you have an Objective-C static library named ObjectivelyStatic and a dynamic Swift framework named DynamicallySwift and you want to make both available in an app that contains both Objective-C and Swift code. Lets call the app Gizmo for example purposes. The catch is that DynamicallySwift also needs to export some of it's own Objective-C code. Things are getting complicated from the module perspective if you want to use ObjectivelyStatic and DynamicallySwift in both your Objective-C and Swift code in your Gizmo app.

How do we make it happen?

First, you need to create an umbrella header and module map for ObjectivelyStatic. I am not going to cover that here, but there are some great resources like Sam Symons' post on the subject.

I'll wait here while you modularize.

Now that you have modularized your static library you need to link ObjectivelyStatic to DynamicallySwift. To remain package manager neutral we will do this the old fashioned way. I like to create a Xcode workspace to contain my project. I usually create a parent directory for the project, place the workspace in the parent directory and then check out the repositories for each library/framework/app in the directory. Then add them to the workspace.

Two pro tips: 

  • Static libraries sometimes need a Header phase added to the Build Phases for the target. You will want to make your umbrella header and any exported headers public using that.
  • In your dynamic framework add `-force_load $(CONFIGURATION_BUILD_DIR)/libObjectivelyStatic.a` to your Other Linker Flags in your dynamic framework to get all your static lib symbols linked in.

Once in the workspace, in DynamicallySwift framework target, add ObjectivelyStatic to the Linked Frameworks and Libraries section. Then, in Build Settings, add the path to the module map parent directory to Header Search Paths and to the Swift Compiler Import Path. Tada! You have made ObjectivelyStatic a proper module that can be used in DynamicallySwift in both Swift and Objective-C files.

Finally, in your Gizmo App target, embedded DynamicallySwift as you would any other dynamic framework. In the Gizmo App target, add the same Header Search Path and Swift Compiler Import Path as you did in DynamicallySwift. Boom! You have access to both ObjectivelyStatic and DynamicallySwift in both Swift and Objective-C files in your app.

You can find an example here.