Implicit Swift Dependencies

If you’re developing in Swift for Mac or iOS, you need to ensure that any standard Swift libraries are also copied into your app bundle. Typically this is handled automatically by Xcode when it detects the presence of any Swift files in your app. If your app is entirely Objective-C, but you link against your own frameworks that themselves depend on Swift, you have to ensure the required libraries are embedded. This can be done by setting the “Always Embed Swift Standard Libraries” checkbox in your target’s build settings to “Yes”:

AlwaysEmbed

When this option is set, Xcode scans the bundled frameworks in your app and, if any of them contains Swift code, it copies the pertinent libraries into your app’s bundle.

This approach falls down a bit when it comes to Unit Testing targets. Because a Unit Test bundle doesn’t typically copy the framework it is testing into its own bundle, Xcode seems to deduce dependencies by examining the list of frameworks the test bundle links to. If you enable the “Always Embed” option above for a Unit Testing bundle, then any framework the bundle links against will be examined for Swift code, and the required libraries copied in.

But what if none of the linked frameworks themselves require Swift, but they depend upon other frameworks that do? In projects of a substantial size, you may have higher-level frameworks implemented entirely in Objective-C, that depend upon lower level frameworks that use Swift. In this scenario, Xcode’s CopySwiftLibs build phase does not identify the deeper dependency, and neglects to embed the required Swift libraries. When your unit tests run, you’ll see a runtime error like this:

The bundle “RSTextFilteringTests” couldn’t be loaded because it is damaged or missing necessary resources. Try reinstalling the bundle.
(dlopen_preflight([...]/RSTextFilteringTests): Library not loaded: @rpath/libswiftAppKit.dylib
  Referenced from: [...]/RSAppKit.framework/Versions/A/RSAppKit
  Reason: image not found)
Program ended with exit code: 82

So “Always Embed Swift Standard Libraries” doesn’t always embed them. I’ve filed Radar #30832923, requesting that the CopySwiftLibs build phase look harder for Swift dependencies. In my opinion it should look not only at the directly linked frameworks, but at any developer-supplied frameworks that those in turn depend upon. This would cause “Always Embed” to behave as expected when a developer adds Swift dependencies to frameworks at any level of their code base.

There are many possible workarounds to the problem, and each has its own drawbacks. Here are a few:

  1. Add a Swift-based unit test file to your test bundle. This simple workaround will work in most situations, because the test itself will impose many Swift library dependencies on your bundle. There is a shortcoming, though: if the test code doesn’t generate all the Swift dependencies that your frameworks do, you’ll still be left with missing Swift libraries. For example if a dependent library requires the Swift library for Apple’s Contacts.framework, your Swift test file is not sufficient to cue Xcode to copy the required libSwiftContacts library.
  2. Manually link to Swift-dependent frameworks. Suppose your unit tests target HighLevel.framework, and LowLevel.framework depends upon Swift. By adding LowLevel.framework to the list of linked libraries for your testing bundle, you will cue Xcode to also embed the required Swift libraries for that framework. This has the advantage of ensuring that whatever Swift libraries are required get copied, but it has the drawback of requiring explicit dependency on a lower level framework from your unit test bundle.
  3. Stop! Abort mission! Revert to Objective-C only. Many Swift dabblers probably feel this emotion from time to time. The unexpected costs of adding a modest amount of Swift to a project are frustrating, and one can’t help but wonder whether they would be better off waiting until Swift support is more baked in to the system. The obvious downside of this approach is that Apple’s progress with Swift is moving quickly, and the longer you put off testing the waters, the harder it may be for you to catch up when ignoring it is no longer a viable option.

The workaround I’ve settled on for now is #2. Adding unnecessary direct dependencies to my test bundle feels a little inappropriate, but it is both easily undone in the future, and unlikely to cause any real problems in practice.

In the course of investigating this problem, it also occurred to me that unit test bundles in particular are such a special case, that Apple should probably give them special treatment to eliminate a number of pitfalls. I filed Radar #30832983 suggesting that unit test bundles should default to “Always Embed Swift Standard Libraries,” on the assumption that Swift code may be present in some library.

Thinking further on that point, it seems that Unit Tests could be safely run in an environment in which all Swift support libraries are available at runtime to be resolved by DYLD. Because test bundles are not built to be shipped to customers, it’s of primary that they link and run successfully. Such an approach would do away with the need for Unit Test bundles to either specify that Swift libraries be bundled, or to fret about which of those libraries in particular need to be copied.