Cocoapods Dependencies in Dynamic Framework Module - ios

I have an app that has three targets:
App Target
Shared Dynamic Framework
Keyboard Extension
The Xcode project specifies Shared as a target dependency in App and Keyboard (the library has code that is required by both).
My Podfile has a "target" block for each target where I define dependencies. Now, my assumption used to be that any libraries included in "Shared" as Cocoapods dependencies would by virtue of doing so also be available to the App and Keyboard Xcode targets. This however does not seem to be the case at this point. If I instead try to import the a given dependency in all 3 targets, I get duplicate symbols.
Can someone explain to me the proper way to achieve what I'm looking for?

Related

How to build Swift framework with 3rd party SPM dependencies

I've built swift frameworks before, but I've never dealt with one that has 3rd party dependencies.
What I have right now is a Swift framework that has a dependency on several Swift packages. When I build it I see MyFramework.framework in the products folder as well as bunch of dependencies. I can make XCFramework out of it, but when integrating (embedding) it into another app I see an error that MyFramework is missing its dependencies (see image below).
What am I doing wrong or missing?
Creating a framework (or xcframework) that has embedded other frameworks is not supported for iOS(I believe this is answered here).
But there is an other way. You can statically link all the dependencies of your library, so in the final framework the binary will also contain the code of its dependencies.
If you are using CocoaPods and the name of the framework is aFramework you can do:
target 'aFramework' do
pod 'Alamofire', :linkage => :static
end
Then you can use the created aFramework.framework in a application target that does not have the Alamofire framework, since the code will be "embedded" in the aFramework's binary.

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.

How to link a (subproject) framework against a pod?

I am creating a new app MyApp, where I decided to put some parts of the app in a seperate framework MyFramework, instead of the app itself to make them reusable in other projects. The framework has its own project (MyFramework.xcodeproj) which is a subproject of the main app project (MyApp.xcodeproj). The main app project has a dependency (does linking & copying) on the framework (MyFramework.framework), of course. This part works fine so far.
Now, MyFramework needs a cocoa pod (Alamofire) in order to work. As far as I understood, I don't want to set up cocoa pods for the MyFramework.xcodeproj project directly, so I just set it up for the main app MyApp.xcodeproj (now MyApp.xcworkspace because of pods) and also included the Alamofire pod there. I then linked MyFramework.xcodeproj against the Alamofire.framework (not the MyApp_Pods.framework), but did not set Alamofire for copying. The MyApp.xcodeproj does the usual cocoapods linking/copying/bundling stuff, so also includes the Alamofire.framework in the final product. This worked partially fine.
(Note: I say 'partially fine' here because I occasionally had errors in the MyFramework sources not finding the Alamofire module when compiling, which could be fixed by re-linking the Alamofire.framework in the MyFramework.xcodeproj. Besides that, building and running the app works fine.)
The issue is now, when I try to archive the app, the Alamofire module is again not found in the MyFramework sources. The error occurs both, when archiving the MyApp (which also builds MyFramework), as well as archiving MyFramework directly. The error occurs not in the linking phase, but in the compile phase of a source Swift file that has an import Alamofire statement.
I suspect something like the linking of the Alamofire.framework to use a wrong relative path that can be found while building, but is different on archiving or such. I already tried to set the Alamofire.framework path to Relative to build products, but couldn't get this to work, not even for regular building (not archiving).
I'm now stuck and wondering whether my whole setup with linking from the MyFramework.xcodeproj project to the Alamofire.framework and having Alamofire included as a pod is correct.
Can anybody give me a hint what might be wrong here? Either in the paths or in the whole setup?
This won't tackle the problem itself, but would provide a solution that serves your need:
Transform MyFramework into a pod. Host it in your private spec repo, or — even simpler — use the git parameter in the pod file to access it.
pod 'MyFramework', :git => 'https://gitsever/maxscompany/MyFramework.git'

Embedding frameworks inside closed-source Swift framework

Our company wants to distribute a closed-source SDK for iOS to our clients. I've been using Cocoapods to build the framework and built an example app making use of it. Previously the app worked fine on the simulator as well as when deployed on the device. However, I was also embedding the Pods.framework file in the app itself. One other piece of information that may be of interest is that the framework is written in Swift, the included cocoapods dependencies are both Swift and Objective-C.
I wanted to make the pods requirements easier to manage so the user doesn't need to be concerned with them and tried to embed the Pods.framework file inside of the SDK we're building - so I removed the steps to Embed Pods Frameworks and Copy Pods Resources from the example app, leaving them only in the framework, I also removed Pods.framework as a dependency of the example app, leaving it only in the SDK. This seemed to work in the simulator, but the app now crashes on mobile device with dyld: Library not loaded error.
Upon researching it, I stumbled into a few related discussions:
https://github.com/CocoaPods/CocoaPods/issues/344 https://objectpartners.com/2014/06/25/developing-private-in-house-libraries-with-cocoapods/
However, the suggested solution of using private pods does not look like it would work for us, it's my understanding that the source code in the private pod would still be open, and we can't share it with our clients.
Could someone advise on a solution that would work in this case?
OK, I finally have a more durable solution. It's a modified, cleaner version of my old one now that I understand how Xcode links in my Swift sub-frameworks better
Problem that makes distribution/compilation a bit ugly:
Since Swift standard libraries aren't bundled on the device like Obj-C, nor are they guaranteed to be stable between versions yet (stable binary interface promised in Swift 3: https://github.com/apple/swift-evolution#development-major-version--swift-30) we have to make sure the entire project is compiled against the same version of Swift. That means the guy using your closed-source framework has to be using the same version of Swift in their Xcode for their project as you did for compiling the library, even if he's not using Swift in his code because ultimately it's his version of Swift that gets bundled into the app and your SDK runs against. This is only an issue for closed-source frameworks because open-source ones will always be compiled against the same version as final project. Possible workaround is to restrict clients to same version you use or distribute multiple compilations (i.e. Swift 2.1 and Swift 2.0). To remedy this, you could provide users with copies of binaries compiled against multiple versions of Swift.
Aside from that, here is what I had to do during compilation/distribution to make a binary framework that works in Swift:
When building the framework:
In project target, make sure to add Pods.framework to Linked Frameworks and Libraries (make sure this is a pre-compiled RED version of Pods.framework, I had a black compiled Pods.framework in the same directory which built fine but then resulted in a framework that would cause the project to complain about missing armv7 architecture during linker phase in later project)
In Build Settings, under User-Defined section, add a field called BITCODE_GENERATION_MODE and set it to bitcode
DO NOT #import any frameworks in your bridging header, all instructions telling you to do that are leftover from Swift 1.0-1.2 days, you don't need it anymore and it does more harm than good (the later project will complain that it can't find these headers that aren't even exposed to it)
Change build target to Generic iOS Device, Archive and Export the framework
When building the project using the framework:
Drag and drop the framework into the project, in General tab add it to Embedded Binaries and Linked Frameworks and Libraries (you only need to add the framework itself, not the sub-frameworks or the pods file)
In Build Settings tab, add a new path to Framework Search Paths: $(PROJECT_DIR)/MyFramework.framework/Frameworks
Build the project

Static library with cocoapod dependency: define in Podspec s.dependency, in Podfile, or both?

I am converting a static library that we use in-house into a CocoaPod, so that our host applications can simply pull it in by referencing it in their Podfiles. This static library in turn depends upon a third-party Pod called HockeySDK.
In my static library's Podspec, I indicate the HockeySDK dependency as follows:
# MyStaticLib requires the latest HockeySDK framework:
s.dependency "HockeySDK", "3.6.4"
I also indicate the dependency in the Podfile that is in the root directory of my static library:
target "MyStaticLib" do
pod "HockeySDK", "3.6.4"
This seems to work fine - if I reference MyStaticLib in the Podfile of MyApp, it pulls in MyStaticLib and also magically pulls in the HockeySDK dependency, and all is good in the world.
What is the difference between these two seemingly redundant mechanisms for indicating the HockeySDK dependency? Why would I use one or not the other or both?
The first scenario is used to indicate that your library has a run-time (and also compile-time, because in cocoapods essentially all run-time things are also compile-time) dependency on HockeySDK.
The second scenario says your target depends on HockeySDK in order to build.
The difference is pretty subtle but is more obvious if you're not statically linking. Since you're linking statically, a compile-time and run-time dependency is the same thing (meaning the second scenario is the same as the first).
If you were linking dynamically against HockeySDK, then the host application needs to be made aware of this so it can provide the dependent library when linking your library. The way to do that is by propagating the dependency via the podspec.
In the podfile, you declare 1. a target and also 2. that you need the object file as well as the headers from HockeySDK in order to compile the target. This only impacts the building of your own library. People consuming your library use the spec not the Podfile.

Resources