iOS Xcode SPM failed to demangle superclass - ios

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?

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:
Duplicate the FrameworkTarget and create a FrameworkTargetT (make sure that are both building with no issues).
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.
Add RxTest to FrameworkTargetT
Remove RxTest from FrameworkTarget
Set in your tests #testable import FrameworkTagetT
Adjust the FrameworkTarget to not run the testsTarget
Set the FrameworkTargetT to run the testsTarget
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

Integrating XCFrameworks into the development and release workflows

I have an Xcode workspace with an iOS framework target and an iOS SampleApp target that depends on and embeds the framework. The framework is closed-source and thus gets delivered in binary form to customers, in a release package alongside a copy of the sources for the entire SampleApp project, so that they can test it out with our app and see how the integration is done.
With this setup, during the development phase, I can open the workspace in Xcode and build the SampleApp target, and because it has an explicit dependency on the framework, Xcode immediately triggers a build of the framework first and then the app is built and linked correctly.
Now I want to leverage the new XCFramework format to distribute this framework to customers instead of using lipo'ed fat frameworks. I update my shell script that produces the release package to create an xcframework bundle at the end with all the various platforms supported, and also update the SampleApp project to depend on this xcframework instead of on the individual frameworks as it did before.
But here comes the problem with the updated SampleApp: After removing the dependency on the frameworks, the workspace setup described above doesn't work anymore, i.e. Xcode will no longer automatically build the framework target when the SampleApp target is built, because the explicit dependency link between the two is now broken.
My first attempt at solving this involved making the xcframework part of my regular development workflow as well. For this I added a run-script phase to the framework build target that generates a current-platform-only xcframework bundle and copies it to the place where the SampleApp expects to find it, and then added the framework target to the targets of the SampleApp build scheme, in order for the framework to be built when the SampleApp is built. Unfortunately this approach does not work because Xcode 12 always seems to build both targets in parallel, no matter what the 'Parallelize Build' setting is set to, and because of that the app will fail to build simply because the xcframework is not yet ready when it's processed by the build system, which is very early in the build phases of the SampleApp. So unless there's a way to force Xcode to honor the order in which the build targets are defined in the scheme, this approach is a no-go.
If this approach cannot work then I'd have to ship the xcframework with the SampleApp unmodified, and instruct customers wanting to run the app to manually remove the references to the universal frameworks and then add/drag the xcframework to it. This would work but it's ugly and does not seem user friendly enough to me.
I'm also considering a different approach whereby the release script programmatically modifies the SampleApp.xcodeproj to depend on the xcframework instead of on the universal frameworks, but that seems like it could be a very fragile thing to do, so would rather avoid it if there's a better way.
This scenario does not seem unique to me so I'd guess someone else must have faced this problem too. If so, how did you solve it?
So I ended up solving this by programmatically modifying the SampleApp's project file at release time to link and embed the xcframework bundle instead of the universal frameworks.
Initially tried to use the tool pbxproj but it didn't quite work, the initial fat framework removal phase was not succeeding.
Then I figured I could try and diff the project.pbxproj file after having manually replaced the frameworks with an xcframework, and turns out it takes only a few small edits to get there, so they can be done programmatically with a simple shell script like the following:
#!/bin/bash
PROJECT_FILE=SampleApp/SampleApp.xcodeproj/project.pbxproj
FRAMEWORK_NAME=MyFramework.framework
XCFRAMEWORK_NAME=MyFramework.xcframework
sed -i '' "s|FileType = wrapper.framework; path = ${FRAMEWORK_NAME}; sourceTree = BUILT_PRODUCTS_DIR;|FileType = wrapper.xcframework; path = ${XCFRAMEWORK_NAME}; sourceTree = \"<group>\";|" ${PROJECT_FILE}
sed -i '' "s|${FRAMEWORK_NAME}|${XCFRAMEWORK_NAME}|g" ${PROJECT_FILE}
There might be small differences in other project setups but mostly it boils down to that. Just beware that if the xcframework is meant to be placed somewhere other than in the project root then you need both a name and a path properties in the first sed command, e.g.:
name = "MyFramework.xcframework"; path = "relative/path/to/MyFramework.xcframework"

Xcode "Class ... is implemented in both ... . One of the two will be used"

I have created a custom Framework. The Framework has some SwiftPM dependencies like Alamofire that it makes use of.
If I add the Framework to my app, I can't build the app without adding the dependencies to my app as well. After doing that and running the app, I can even remove the Frameworks from the app's SwiftPM dependencies and it's working fine but only until I clear the build folder.
Having the dependency in both the app and the Framework I get dozens of prints that say
objc[13048]: Class _TtC11CryptoSwift3AES is implemented in both
/Users/Z/Library/Developer/CoreSimulator/Devices/68/data/Containers/Bundle/Application/F0/MyApp.app/Frameworks/MyKit.framework/MyKit (0x106c8e960) and
/Users/Z/Library/Developer/CoreSimulator/Devices/68/data/Containers/Bundle/Application/F0/MyApp.app/MyApp (0x1052ccd20).
One of the two will be used. Which one is undefined.
After cleaning the build folder it again tells me Missing required module 'Alamofire'.
How can I make either the Framework happy with the dependencies from the app or the app happy with the dependencies from the Framework? The app doesn't access Alamofire directly, only the Framework does.
So I'd like to either build the Framework without including the dependencies to it or build the app without including the dependencies to it which will hopefully resolve this warning.

How to reuse Swift code in other projects?

I wrote a class in Swift. I want to use this code in two separate iOS app projects that I wrote. Both the shared code and the apps are written in Swift. What is the best way of doing that?
I tried to create both a framework and a library in Swift and then add it as a sub-project to my app. In both cases I could not make the app see the module. I tried to add the shared module to "Target Dependencies" and "Link Binary With Libraries" of the main app's target. No luck - the app still can not see the classes of the shared module.
Using Xcode6 Beta6 at the moment.
Solution
As Konstantin Koval and Heliem pointed out, all I needed is to use public in my class and method declarations, in the shared module. Now all works, both if I use workspace and if I add the module as a subproject.
Update
I just found an excellent easy solution for reusing code between projects in Swift. It is called Carthage (https://github.com/Carthage/Carthage). This is not a plug as I am not affiliated with it in any way, I just really like it. It works in iOS 8+.
Create a new project (iOS Cocoa Touch Framework) for your reusable code
Move your classes to that Framework project
Mark your methods and classes, that should be visible to others as public
Create Workspace.
You can create a workspace on step 1. When you create new Framework project, Xcode will ask you if you want to create new workspace and add that project to workspace. This is the best approach
Add both your project and Framework to the workspace
Select you project target -> General tab. Add Framework and Libraries (add your library here)
When you want to use code from your Library in swift file, import it using import 'LibTargetName'
You can take a more programatic approach by using SWM (Swift Modules): https://github.com/jankuca/swm
It is very similar to npm (node.js package manager) or bower (client-side module manager); you declare your dependencies in a swiftmodule.json file like below. It does not have a central module registry like the two mentioned JS solutions, it only accepts git URLs.
{
"name": "ProjectName",
"dependencies": {
"Dependency": "git://github.com/…/….git"
}
}
…run swm install and have the Dependency module (compiled into a *.swiftmodule binary) ready for import in the .modules/ directory.
import Dependency
And if you prefer to skip Xcode for app development, you can also build your whole app using swm build (more info in the project's readme).
The project is still in early stages but it makes itself useful a lot for me at least. It provides the most clean (and clear) way of creating importable modules.
Here is a video which is very straightforward: http://eonil-observatory.tumblr.com/post/117205738262/a-proper-way-to-add-a-subproject-to-another-xcode
The video is for OS X instead of iOS. But you will get it and figure out the process for iOS.
Let's assume that AppA needs to reused code from SharedProject.
The following process works for me in Xcode 7 Beta:
Create SharedProject. Project type must be Framework instead of Application. You write common code in this project and mark the code as public.
Open AppA in Xcode, open the folder which contains SharedProject in Finder. Drag the .xcodeproj file of SharedProject from Finder and drop it into the root folder of AppA in Xcode Project Navigator.
AppA --> Build Phases --> Link Binary with Libraries. Add SharedProject.
import SharedProject and reuse code from SharedProject!
Edit:
Nowadays I suggest you use Carthage. It's better than the home made solution above.

Link framework against App and Test Target

I have a custom Framework I use within my normal App target as well as the corresponding UnitTest target. Turns out that confuses the runtime in such way that it is unable to choose the correct implementation since it has multiple choices:
objc[35580]: Class AClass is implemented in both ../MyApp.app/MyApp and ../MyApp.app/MyAppTests. One of the two will be used. Which one is undefined.
That of course leads to weird behavior if you try to check an object's class hierarchy or do any other class related checks.
So it boils down to the following two questions:
I don't see similar logs for e.g. UIKit components, but this framework is also linked to both targets. Have I incorrectly compiled the framework?
Is it just a trivial configuration issue I missed?
PS: I already checked similar posts like 1 or 2, but although everything is configured as described, the problem remains.
You have added the dependency framework to the Tests target. This is flawed thinking. Since your primary application ALSO exports the SAME framework you will receive warnings about duplicated symbols for any classes found in the framework.
By removing your framework from the test target you can resolve the warnings. Remember, you're not losing any functionality by not linking against the same framework in the test target. Trust me, your code is still there.
I ran into a similar problem here: Xcode5: creating new testing target
The key is to create a new unit testing bundle, point it to your original target, and then don't do anything else! If you start including frameworks and source files into the test target, it'll generate these linking errors. The test target is supposed to "inject" the test classes into the actual target, not create a new separate target on it's own. So you just need to import the header files in your test class, and write your test cases.
I think the bundle should only "read" the framework's header files but not build the sources and leave that task to the App (remove the Framework .m files from the UnitTest target).
Right now the App and the UnitTest are both building the Framework, thus the duplicated classes.

How to use a static library (e.g. cocoapods library) on a XCTest?

I'm working with Core Data and, as I the model gets more complex, I need to make sure that the new changes I introduce don't break my model unexpectedly in other parts.
I can create unit tests and run them every time I change something on my model. If something breaks, there might be something wrong with my model or at least I know I have to modify some queries in the main code/tests.
I'm using MagicalRecord to have access to some convenience methods.
I also use cocoapods for the same reason, convenience.
The problem is that cocoapods creates a static library and links it against my target, but in Xcode, new test targets are not automatically configured to link against the same libraries/frameworks the target in question links against to.
How can I have a XCTest link against a static library?
This is not only helpful with MagicalRecord/Core Data, but when you're using an external library it's a good idea to have tests to make sure that updates on the library don't break your App.
If you're using cocoapods, you can simply use link_with to include your test target, but if you're using a static library not created by cocoapods you can do the following:
(I will still use a cocoapods library for the instructions, as that's what I'm working with, but the idea is the same if you're not using a cocoapods library)
Once you have created a new Test Target, click on the project root node in the project navigator and select your test target.
Go to Build Settings and search for Header Search Paths. Double click on the Header Search Paths item and enter
${SRCROOT}/Pods/Headers and select recursive if you want to import all of your cocoapods libraries headers or enter them individually:
${SRCROOT}/Pods/Headers/MagicalRecord leaving non-recursive selected (although in this case it doesn't really matter).
Now search for Linking and in Other Linker Flags add -ObjC
Now with your Test Target still selected, go to Build Phases and in Link Binary With Libraries click on the + and add libPods.a or the other libraries individually (libPods-MagicalRecord.a)
You should be able to run a XCTest using the static library.
Optional: I like to import the headers I know I'm going to use in the -Prefix.pch file. You can go to your target test group in the Project Navigator. Go to the Supporting Files group and open the -Prefix.pch file. For MagicalRecord I like to add:
#define MR_SHORTHAND
#import "CoreData+MagicalRecord.h"
For more information:
Unit Testing with Core Data
Creating a Static Library in iOS
Tutorial
After a lot of fighting, these steps worked for me:
1) Project> Info
On configurations, set the Test Target to share the same Configuration File as your main project (generated by Cocoapods).
Now, you should start to get some errors because XCUnit framework is missing, but now your external libraries imported with CocoaPod are visible on your test project.
2) On the Test Target>Build Settings look for Header Search Paths, once there add:
$(DEVELOPER_DIR)/Platforms/iPhoneOS.platform/Developer/Library/Frameworks
$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks
The Unit Test framework is inside your Xcode App, this headers will make them public to be added later.
3) On the Test Target> Build Phases add the SenTestingKit.framework
And it should look like this
From there, everything seems to work for me. Good Luck.

Resources