I’m currently working on a Swift project where the workspace contains multiple projects (ACM…) due to company decision.
When adding Firebase with Cocoapods, I need it in two of the projects (in ‘myapp’ and ‘ACMLoyalty’). So my .podfile
looks like this:
workspace 'myapp.xcworkspace'
platform :ios, '12.0'
use_frameworks!
def shared_pods
source 'https://github.com/CocoaPods/Specs.git'
pod 'Nuke', '9.2.3'
pod 'Alamofire', '~> 4.9.1'
pod 'DeepDiff', '2.3.1'
end
def firebase_pods
pod 'Firebase/Analytics', '7.4.0'
pod 'Firebase/Crashlytics', '7.4.0'
pod 'Firebase', '7.4.0'
end
def appcenter_pods
pod 'AppCenter', '4.1.0'
pod 'AppCenter/Distribute', '4.1.0'
end
project 'myapp.xcodeproj'
project 'ACMCore/ACMCore.xcodeproj'
project 'ACMData/ACMData.xcodeproj'
project 'ACMLoyalty/ACMLoyalty.xcodeproj'
project 'ACMMVVM/ACMMVVM.xcodeproj'
project 'ACMNetwork/ACMNetwork.xcodeproj'
project 'ACMShared/ACMShared.xcodeproj'
target :myapp do
project 'myapp.xcodeproj'
shared_pods
firebase_pods
appcenter_pods
end
target :ACMCore do
project 'ACMCore/ACMCore.xcodeproj'
shared_pods
end
target :ACMData do
project 'ACMData/ACMData.xcodeproj'
shared_pods
end
target :ACMLoyalty do
use_frameworks! :linkage => :dynamic
project 'ACMLoyalty/ACMLoyalty.xcodeproj'
shared_pods
firebase_pods
target 'ACMLoyaltyTests' do
inherit! :search_paths
shared_pods
firebase_pods
end
target 'ACMLoyaltyTestHost' do
inherit! :search_paths
shared_pods
firebase_pods
end
end
target :ACMMVVM do
project 'ACMMVVM/ACMMVVM.xcodeproj'
shared_pods
target 'ACMMVVMTests' do
inherit! :search_paths
shared_pods
end
end
target :ACMNetwork do
project 'ACMNetwork/ACMNetwork.xcodeproj'
shared_pods
target 'ACMNetworkTests' do
inherit! :search_paths
shared_pods
end
end
target :ACMShared do
project 'ACMShared/ACMShared.xcodeproj'
shared_pods
target 'ACMSharedTests' do
inherit! :search_paths
shared_pods
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if Gem::Version.new('9.0') > Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'])
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
end
end
end
end
Problem:
During run-time I get following warnings in the console:
objc[2946]: Class FIRAnalyticsConnector is implemented in both /private/var/containers/Bundle/Application/05B31227-083E-42A7-9E27-DB7924A754B9/myapp.app/Frameworks/ACMLoyalty.framework/ACMLoyalty (0x107c4d1b8) and /private/var/containers/Bundle/Application/05B31227-083E-42A7-9E27-DB7924A754B9/myapp.app/myapp (0x104217298). One of the two will be used. Which one is undefined.
This is just one line as sample, but there are a lot of other Firebase classes listed with the same description.
After some research I know, that the problem is, that Firebase is a static library and will be simply copied to the specific target which causes this issue. With the other libraries (dynamic) this problem doesn’t occur.
Does anybody have a solution for my problem?
Edit: Answer @mbi
I already tried a solution with abstract_target
.
Please find my .podfile
for this attempt:
# Uncomment the next line to define a global platform for your project
workspace 'myapp.xcworkspace'
platform :ios, '12.0'
use_frameworks!
project 'myapp.xcodeproj'
project 'ACMCore/ACMCore.xcodeproj'
project 'ACMData/ACMData.xcodeproj'
project 'ACMLoyalty/ACMLoyalty.xcodeproj'
project 'ACMMVVM/ACMMVVM.xcodeproj'
project 'ACMNetwork/ACMNetwork.xcodeproj'
project 'ACMShared/ACMShared.xcodeproj'
abstract_target 'myapp-app' do
source 'https://github.com/CocoaPods/Specs.git'
pod 'Nuke', '9.2.3'
pod 'Alamofire', '~> 4.9.1'
pod 'DeepDiff', '2.3.1'
pod 'Firebase/Analytics', '7.4.0'
pod 'Firebase/Crashlytics', '7.4.0'
pod 'Firebase', '7.4.0'
def appcenter_pods
pod 'AppCenter', '4.1.0'
pod 'AppCenter/Distribute', '4.1.0'
end
target :myapp do
project 'myapp.xcodeproj'
appcenter_pods
end
target :ACMCore do
project 'ACMCore/ACMCore.xcodeproj'
end
target :ACMData do
project 'ACMData/ACMData.xcodeproj'
end
target :ACMLoyalty do
project 'ACMLoyalty/ACMLoyalty.xcodeproj'
target 'ACMLoyaltyTests' do
end
target 'ACMLoyaltyTestHost' do
end
end
target :ACMMVVM do
project 'ACMMVVM/ACMMVVM.xcodeproj'
target 'ACMMVVMTests' do
end
end
target :ACMNetwork do
project 'ACMNetwork/ACMNetwork.xcodeproj'
target 'ACMNetworkTests' do
end
end
target :ACMShared do
project 'ACMShared/ACMShared.xcodeproj'
target 'ACMSharedTests' do
end
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if Gem::Version.new('9.0') > Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'])
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
end
end
end
end
The problem is, with this solution I have even more warning, because Firebase is built in ALL projects and conflicts.
EDIT2: With the answer of @arturdev I got it working without the ... implemented in both...
warning with following podfile:
# Uncomment the next line to define a global platform for your project
workspace 'myapp.xcworkspace'
platform :ios, '12.0'
use_frameworks!
project 'myapp.xcodeproj'
project 'ACMCore/ACMCore.xcodeproj'
project 'ACMData/ACMData.xcodeproj'
project 'ACMLoyalty/ACMLoyalty.xcodeproj'
project 'ACMMVVM/ACMMVVM.xcodeproj'
project 'ACMNetwork/ACMNetwork.xcodeproj'
project 'ACMShared/ACMShared.xcodeproj'
abstract_target 'myapp-app' do
source 'https://github.com/CocoaPods/Specs.git'
pod 'Nuke', '9.2.3'
pod 'Alamofire', '~> 4.9.1'
pod 'DeepDiff', '2.3.1'
def firebase_pods
pod 'Firebase/Analytics', '7.4.0'
pod 'Firebase/Crashlytics', '7.4.0'
pod 'Firebase', '7.4.0'
end
def appcenter_pods
pod 'AppCenter', '4.1.0'
pod 'AppCenter/Distribute', '4.1.0'
end
target :myapp do
project 'myapp.xcodeproj'
firebase_pods
appcenter_pods
target 'myappTests' do
end
end
target :ACMCore do
project 'ACMCore/ACMCore.xcodeproj'
target 'ACMCoreTests' do
firebase_pods
end
end
target :ACMData do
project 'ACMData/ACMData.xcodeproj'
target 'ACMDataTests' do
firebase_pods
end
end
target :ACMLoyalty do
project 'ACMLoyalty/ACMLoyalty.xcodeproj'
firebase_pods
target 'ACMLoyaltyTests' do
end
target 'ACMLoyaltyTestHost' do
end
end
target :ACMMVVM do
project 'ACMMVVM/ACMMVVM.xcodeproj'
target 'ACMMVVMTests' do
end
end
target :ACMNetwork do
project 'ACMNetwork/ACMNetwork.xcodeproj'
target 'ACMNetworkTests' do
end
end
target :ACMShared do
project 'ACMShared/ACMShared.xcodeproj'
target 'ACMSharedTests' do
end
end
end
PROJECT_ROOT_DIR = File.dirname(File.expand_path(__FILE__))
PODS_DIR = File.join(PROJECT_ROOT_DIR, 'Pods')
PODS_TARGET_SUPPORT_FILES_DIR = File.join(PODS_DIR, 'Target Support Files')
post_install do |installer|
## For more information: https://stackoverflow.com/questions/65904011/cocoapods-with-multiple-projects-firebase-causes-class-xxx-is-implemented-in-b
remove_static_framework_duplicate_linkage({
'ACMLoyalty' => ['FBLPromises', 'FIRAnalyticsConnector', 'FirebaseAnalytics', 'FirebaseCore', 'FirebaseCoreDiagnostics', 'FirebaseCrashlytics', 'FirebaseInstallations', 'GoogleAppMeasurement', 'GoogleDataTransport', 'GoogleUtilities']
})
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
#config.build_settings['LD_NO_PIE'] = 'NO'
if Gem::Version.new('9.0') > Gem::Version.new(config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'])
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
end
end
end
end
# CocoaPods provides the abstract_target mechanism for sharing dependencies between distinct targets.
# However, due to the complexity of our project and use of shared frameworks, we cannot simply bundle everything under
# a single abstract_target. Using a pod in a shared framework target and an app target will cause CocoaPods to generate
# a build configuration that links the pod's frameworks with both targets. This is not an issue with dynamic frameworks,
# as the linker is smart enough to avoid duplicate linkage at runtime. Yet for static frameworks the linkage happens at
# build time, thus when the shared framework target and app target are combined to form an executable, the static
# framework will reside within multiple distinct address spaces. The end result is duplicated symbols, and global
# variables that are confined to each target's address space, i.e not truly global within the app's address space.
#
# Previously we avoided this by linking the static framework with a single target using an abstract_target, and then
# provided a shim to expose their interfaces to other targets. The new approach implemented here removes the need for
# shim by modifying the build configuration generated by CocoaPods to restrict linkage to a single target.
def remove_static_framework_duplicate_linkage(static_framework_pods)
puts "Removing duplicate linkage of static frameworks"
Dir.glob(File.join(PODS_TARGET_SUPPORT_FILES_DIR, "Pods-*")).each do |path|
pod_target = path.split('-', -1).last
static_framework_pods.each do |target, pods|
next if pod_target == target
frameworks = pods.map { |pod| identify_frameworks(pod) }.flatten
Dir.glob(File.join(path, "*.xcconfig")).each do |xcconfig|
lines = File.readlines(xcconfig)
if other_ldflags_index = lines.find_index { |l| l.start_with?('OTHER_LDFLAGS') }
other_ldflags = lines[other_ldflags_index]
frameworks.each do |framework|
other_ldflags.gsub!("-framework "#{framework}"", '')
end
File.open(xcconfig, 'w') do |fd|
fd.write(lines.join)
end
end
end
end
end
end
def identify_frameworks(pod)
frameworks = Dir.glob(File.join(PODS_DIR, pod, "**/*.framework")).map { |path| File.basename(path) }
if frameworks.any?
return frameworks.map { |f| f.split('.framework').first }
end
return pod
end
2
Answers
The error
Class XYZ is implemented in both ...
indicates that the same class is beeing compiled and linked into more than one built product. You are making things more complex than they need to be.I’m really having a hard time to figure out by looking at the given Podfile what your actually trying to achieve. Obviously there a few things that could be improved but that heavily depends on what the requirements are.
For example you should read about and use the concept of
abstract_target
and nesting targets in a Podfile. This gives you the possibility to describe dependencies just one time and inherit them to the nested targets. See https://guides.cocoapods.org/using/the-podfile.htmlFurthermore you have to keep in mind that Cocoapods is applying something called dedublication which basically means no matter how often you specify a dependency in your Podfile it tries to squash them into as least targets as possible in the generated Pods project. So to me the many statements of the very same dependencies such as
share_pods
in your Podfile seem some kind of wrong.And in my opinion you are using inheritance in a weird way. For example you limit inheritance to exclude parent’s deps just to add them again in the next line. Example:
As of my experience in most cases nested test targets don’t need the dependencies so inheriting just search paths as you do is just fine. But only you know whether this particular test target really depends on
share_pods
or not.I’m pretty sure once you sort out your actual requirements and rewrite the Podfile to a nested structure of abstract and non-abstract targets there is a very high chance that your problem goes away.
Some of the pod dependencies are being compiled as a static library and not dynamic (f.e. Firebase and other Google libs).
A static framework can’t be linked more than once (f.e. in a framework target and in a main target which also links the framework above), because the linkage of static frameworks happens during build-time, yet for the dynamic frameworks the linkage happens on runtime and the linker is smart enough to avoid duplications.
Therefore you need to write a script in your podfile to remove static library linkages.
A dirty example: