Link framework against App and Test Target - ios

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.

Related

iOS Xcode SPM failed to demangle superclass

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.

Legacy ObjC/Swift codebase crashes tests in `swift_checkMetadataState`: how to disentangle app/test targets?

I've got a mixed ObjC/Swift codebase with a very mixed-up structure, and the tests no longer run under Xcode 10.2 (details below). I'm trying to make the minimum structural changes to get tests back, so that I have a safety net for the refactoring that will come next. After taking what seems to me to be the obvious first step, the tests fail to build (details again below).
I'm interested in any of
solutions for the original problem (to get tests running again in the messy setup)
solutions for the problem after refactoring (to get refactored tests building in the somewhat tidier setup)
suggestions for build settings to verify or include in the description here, to get clearer on what's going wrong
I'm not interested in advice about how to structure a fresh project or create new targets with more sensible configurations: extracting code from these targets is also non-trivial, so I really need test coverage back before I start any refactoring.
Original situation (very messy)
project myCompany
app target app which builds module MyCompany
contains both Swift and ObjC code, with dependencies both ways
Defines Module = Yes, Product Module Name = MyCompany
test target myCompanyTests
Defines Module = No, Product Module Name = MyCompany
also contains both Swift and ObjC code
CocoaPods for external dependencies, also a bunch of internal Swift modules with dependencies managed by hand
Test files are included only in the myCompanyTests target, but many code files are included in both app and myCompanyTests targets. I think this and the two targets defining the same module name was probably in order to avoid having to import the app target into tests, because of problems with Swift/ObjC interop (see the next section). Building tests produces warnings about classes implemented in two places:
objc[9724]: Class _TtC12MyCompany12DiaryFetcher is implemented in both
/Users/tikitu/Library/Developer/CoreSimulator/Devices/556CAC28-B90B-4B6B-A87C-1A1450795051/data/Containers/Bundle/Application/495F33C2-F7FC-4AE6-B3FE-6908D6361B55/mycompany-develop.app/mycompany-develop (0x10d724060)
and
/Users/tikitu/Library/Developer/Xcode/DerivedData/mycompany-bifciplpbqaeqqdrmhivpjgnojri/Build/Products/Debug-iphonesimulator/mycompany-develop.app/PlugIns/myCompanyTests.xctest/myCompanyTests (0x13566dc38).
One of the two will be used. Which one is undefined.
As of Xcode 10.2, myCompanyTests builds successfully but running the tests fails with an EXC_BAD_ACCESS in swift_checkMetadataState somewhere inside the UIApplicationMain call. My guess is that this is related to the module-name/files-in-both-targets shenanigans.
First attempt at "fix the obvious mistakes"
As a first attempt to tidy things up somewhat I've done the following:
Remove all non-test files from the myCompanyTests target
Rename myCompanyTests Product Module Name to MyCompanyTests
Add #testable import MyCompany in lots of swift tests
Then I start running into Swift/ObjC interop problems, because I need to call Swift code in the app target from ObjC code in the test target. Things I've tried:
#import "MyCompany-Swift.h" in an objc .m test file
'MyCompany-Swift.h' file not found
#import <MyCompany-Swift.h> in an objc .m test file
'MyCompany-Swift.h' file not found
#import <MyCompany/MyCompany-Swift.h> in an objc .m test file (yes you can see I don't actually understand this mechanism)
'MyCompany/MyCompany-Swift.h' file not found
#import "MyCompanyTests-Swift.h" in all objc .m test files that need access to the app target
the generated file MyCompanyTests-Swift.h includes a line #import MyCompany; which looks very promising!
but that line fails with the error Module 'MyCompany' not found
Especially this last one looks suspicious to me, as I would expect the Xcode generated file should "just work". I guess I've overridden some user setting somewhere that's getting in the way: I'd be delighted with any suggestions for places to check.
I won't accept this (yet) as it's incomplete information, but I believe at least that I know why the fixed-up version is failing: the location of MyCompany-Swift.h isn't in the unit-test target's header search paths, because it's the app target. Reference e.g. https://github.com/jeremy-w/MixedLanguageTesting (which suggests adding an ad-hoc entry to the header search paths).
I believe the issue to be that only a framework target auto-creates the swift-to-objc header file Module-Swift.h: neither the app target nor a test target appear to do so.
I successfully resolved these issues by creating two new framework targets
* MyCompanyStuffThatUsedToBeInApp (containing everything that used to be in the app target except main.h), which does bidirectional swift/objc interop and
* MyCompanyTesting (a framework target imported by the tests, which likewise does bidirectional swift/objc interop for test-only code).
There may still be simpler ways to tidy this up, but this one at least is proven to work.

Core Data classes not generated for test target

I use Core Data's automatically generated classes. My project has 3 targets in addition to the test target. For each target, the Core Data classes are properly generated which I verified by inspecting the Derived Data folder. However, classes are not generated for the Test Target despite it being ticked in the Core Data model file. This causes an "undeclared identifier" and "Use of undeclared type" errors when I try to reference one of the Core Data classes in the test target. How can I fix this please?
You do not need extra classes generated for each test target - your import process should import everything, and no files should need to be added to other targets.
Declaring #testable import MyProject should take care of everything.
In Objective C
#import MyProject;
In Xcode 9.1 try adding your .xcdatamodel to a test target too. All auto-generated class will be imported too.
This was due to a bug currently in Xcode (8.3.1) where auto-generated NSManagedObject classes (codegen set to "Class Definition") cannot be found on the global path despite the project compiling successfully. The only way around it is to which to manual generation of the NSManagedObject classes by setting codegen for each entity to "Manual/None".
Select the test target, navigate to Build Settings and search for the setting "Header Search Paths"
Then add the following entry:
$CONFIGURATION_TEMP_DIR/{Project Target Name}.build/DerivedSources/CoreDataGenerated/{Project Name}
Replace the curly brackets with your main target name (not the test target), and your project name, respectively.
Xcode should now be able to find the generated source files when building the test target.
I noticed in Xcode 9.1 that the Data Model Inspector has a drop down for the Module to use. Selecting 'Current Product Module' with the Class Definition Codegen, and including the model in your Test target, compiles without errors. From what I can tell, the problem pieSquared noticed doesn't appear to be an issue, but my tests aren't exhaustive yet. It may be something to try, nonetheless.
I have wrestled with this issue ever since Xcode 9.4 or thereabouts. The error was always the same:
Testing cancelled because build failed.
'MyEntity+CoreDataProperties.h' file not found
I've filed a bug report (45802900), but I got Apple Support involved as well and together we finally found the solution. . Actually, there are two solutions.
Solution 1: Set the Header Search Paths build setting of the Test Target
The most elegant solution, to my mind, is to set the Header Search Paths build setting of the test target. Ziqiao Chen of Technical Support figured out the correct path, while I provided the build variables. For projects with only one data model, the name of which is the same as the project (which is the default), the Header Search Path may be:
$(TARGET_TEMP_DIR)/../$(PROJECT_NAME).build/DerivedSources/CoreDataGenerated/$(PROJECT_NAME)
Make sure the path is set to 'non-recursive'.
For projects with multiple data models, a non-recursive path should be added to Header Search Paths for each data model:
$(TARGET_TEMP_DIR)/../$(PROJECT_NAME).build/DerivedSources/CoreDataGenerated/dataModel1
$(TARGET_TEMP_DIR)/../$(PROJECT_NAME).build/DerivedSources/CoreDataGenerated/data_model_B
Solution 2: Add the data model to the Test Target
Another solution, which Ziqiao Chen came up with and which I've also read on here on SO, is to add the data model to the test target. In my experience, however, this only works with a single data model. As Ziqiao Chen pointed out, Xcode should generated the exact same files for the test target as for the main target.
My experience is that in more complicated projects (multiple targets, multiple data models) all kind of linker errors may occur, from complaints about duplicates to the "testing cancelled" error described above. For simple projects, however, it's a quick and simple solution.
For Xcode 11.5:
if Codegen property is class Definition, and if you are not getting a suggestion for the entity you created in xcdatamodel. Try to quit Xcode and reopen your project again. It works for me. This answer is only if you are not getting suggestion but if your file doesn't get generated try any above answer.

Adding Swift files to test target not fixing unit tests

I have looked at a lot of blogs and tried many things to get my Swift unit tests to work in Xcode 6.0.1 (or 6.1 for that matter). I'm trying to access classes in my app's target so I wrote this line:
var vc: LoginViewController!
Of course, I get the "Use of undeclared type 'LoginViewController'" error.
I then try to add LoginViewController to my test target, but then I get "use of unresolved identifier" errors on other classes in my project. So I try to add those classes to my test target, but I end up with a seemingly endless source of errors like the screenshot below:
Declaring all my classes as public, causes other errors and seems like bad practice. Is there anyway to include unit tests in a Swift project that relies on many frameworks and classes? I simply want to start with something almost exactly like the code in this article.
After much headache and putting this on the back burner, I found that the main problem was when adding files to the test target membership, the Objective-C classes would still complain. I figured it was an old compatibility issue with the new way Swift does unit tests, but the solution was my test target didn't know there was a bridging header. Thus, I added a reference to it in my test target's build settings, like so:
It seems simple and obvious now, but the errors were unhelpful. No other solutions I saw for Swift unit tests suggested this could be an issue, but now I know.
Tl;dr
For unit tests to work in Swift, the test target must know everything the app target knows, so add a reference to your bridging header in your test target too (if applicable).
If you're using Xcode 7, there's now a much better way of dealing with this using #testable import {ModuleName}.
Just make sure that the module you want to test has the build setting Enable Testability set to YES.
I am using Xcode 6.1
You need to add your swift file to the target membership of the test target

Don't we need to link framework to XCode project anymore?

Base on this question
Why don't iOS framework dependencies need to be explicitly linked to a static library
I read the selected answer and still don't understand so I made an example project
Test Project on Github
In the test project, I remove all framework from Link Binary With Libraries and File navigation for both main project and the static library (including Foundation.framework and UIKit.framework too), basically, both project link to 0 frameworks.
Questions are
In static library, it's including MapKit/MapKit.h without referencing the Mapkit.framework to the project, why is its still working?
In main project, I remove UIKit.framework and Foundation.framework from the project, why is it still working?
Since it's working for now, will there be any issue later?
Thank you for your comment.
P.S. By working, I mean I can run on the simulator and I can archive the main project without any error.
Edit 25/07/2014
I tried with the real app that I'm working on, it's the same.
I highlight Foundation, UIKit, CoreData and 10 another frameworks in File Navigation, well, all of them.
Uncheck the target in Utilities Panel --> Target Membership
Build : Pass, Run : Pass
Every functionality of my app is still working as expected. I don't get this.
Check your project build settings. Underneath LLVM 5.1 — Language — Modules you should see the option 'Link Frameworks Automatically'. In your case it sounds like it's set to 'YES', the default.
In that case, instead of producing an error when you reference a class that the compiler doesn't know, it'll figure out which Framework contains that class and link it. In your code it'll be MKMapView or one of the other MapKit classes that triggers the linkage.
EDIT: from the relevant 'What's New?' document:
Auto Linking is enabled for frameworks imported by code modules. When
a source file includes a header from a framework that supports
modules, the compiler generates extra information in the object file
to automatically link in that framework. The result is that, in most
cases, you will not need to specify a separate list of the frameworks
to link with your target when you use a framework API that supports
modules.
Another way of looking at it is that the compiler is smart enough to mutate #import to #import when the framework has been built appropriately. All system frameworks have been.
To elaborate #Tommy's answer, a framework that supports modules satisfies the following 2 conditions:
Under Build Settings > Packaging
Define Modules is set to YES
Module Map File exists.
So, if you're certain that the framework you're using in your code modularizes like that, you can choose to not explicitly add it in the link phase as it will be automatically added as long as in the project file, under Apple Clang - Language - Modules, The option Link Frameworks Automatically is set to YES.

Resources