iOS Xcode SPM failed to demangle superclass

Viewed 1529

My app is composed of many projects (frameworks), one for each main feature and a common framework with all sorts of things that I need to access in multiple of my features.

I'm using Xcode 11's Swift Package Manager to add dependencies.

The common framework contains a RxSwift dependency, which I use throughout the whole project.

I'm facing problems when I try to use RxTest in any of my feature frameworks.

If I add RxTest via SPM to the test target directly and run the tests, I get

failed to demangle superclass of 'class name' from mangled name 'other class name'

and many

Class 'class name' is implemented in both 'common framework path' and 'test target path'

where all these classes are Rx related. The 'failed to demangle' error crashes the test and only occurs when I try to initialize a RxTest class.

If I add RxTest to the common framework, the tests run fine, but when I run the app, I get

dyld: Library not loaded: @rpath/XCTest.framework/XCTest

Which makes sense, because I'm adding a test framework to a non-test framework, and it's not something good to do.

So basically, I wasn't able to get a configuration where both the tests and the app run fine. Either the app runs or the tests run.

How can I get this working? Is there a way to include RxTest on the common framework only when I build it on a test target? Or should RxTest only be included on the test targets and I'm missing some configuration?

3 Answers

Xcode with SPM dependencies cannot handle same SPM dependency in multiple targets that are dependent on each other right now. Each dependency needs to be only in single target at the moment. I dont know why as of now, but I'll try investigate more and file bug if it is not filed yet.

Your issue is likely that the library is using static linking instead of dynamic linking. In SwiftPM you can specify a library as being static or dynamic if you want or you can just let the build system decide which is what most packages do. Xcode seems to favor the static approach when it builds with SwiftPM which results in the build issues you are experiencing.

If you modify the Package.swift to have RxTest be a dynamic library instead it should work. You can easily test this by cloning RxSwift and modifying this line:

.library(name: "RxTest", targets: ["RxTest"]),

into:

.library(name: "RxTest", type: .dynamic, targets: ["RxTest"]),

and then dragging the local copy of RxSwift into your Xcode Project Navigator. It will then use your local copy of the package instead of the one cloned by Xcode.

Once you do this you can link it against any targets you need and it should work. If that does actually fix the problem then your long term solutions are likely:

1) Have a fork that simply changes it to a dynamic library.

2) Convince the RxSwift community to change their products to dynamic or to vend dynamic versions in addition to the default.

3) Don't use RxTest or similar things in multiple places.


It is also worth noting, that Xcode 11.3 and earlier do not support archiving with dynamic Swift Packages. So if you go down the dynamic route you will have to wait for Xcode 11.4.

Workaround:

I had the same issues. My project configuration is:

  • Workspace
    • ProjectAppOne
      • AppTargetOne (Embed and sign FrameworkTarget)
    • ProjectAppTwo
      • AppTargetTwo (Embed and sign FrameworkTarget)
    • ProjectCoreFramework
      • FrameworkTarget
      • testsTareget
      • SPM Dependencies (including RxSwift)

My workaround was:

  1. Duplicate the FrameworkTarget and create a FrameworkTargetT (make sure that are both building with no issues).
  2. FrameworkTarget is still used to build the applications targets, so be sure that is the only one used in the imports of the application targets.
  3. Add RxTest to FrameworkTargetT
  4. Remove RxTest from FrameworkTarget
  5. Set in your tests @testable import FrameworkTagetT
  6. Adjust the FrameworkTarget to not run the testsTarget
  7. Set the FrameworkTargetT to run the testsTarget
  8. Adjust your CI fastlane/scripts, that involve testing target and call the FrameworkTarget directly

At the end the project structure was looking like:

  • Workspace
    • ProjectAppOne
      • AppTargetOne (Embed and sign FrameworkTarget)
    • ProjectAppTwo
      • AppTargetTwo (Embed and sign FrameworkTarget)
    • ProjectCoreFramework
      • FrameworkTarget
      • FrameworkTargetT
      • testsTareget
      • SPM Dependencies (including RxSwift)

I do not think is the ideal solution, it is easy to import the wrong framework in code or test, so double check that you are happy with that. But while we are waiting for the SPM team to sort the problem, it could be used and removed easily. This is also easy if you have already a framework separated code.

PS: you need to remember to add every new file to both of the targets FrameworkTarget and FrameworkTargetT.

Related