skip to Main Content

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


  1. 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.html

    Furthermore 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:

    target :ACMMVVM do
      project 'ACMMVVM/ACMMVVM.xcodeproj'
      shared_pods  #  <-- dependency here
    
      target 'ACMMVVMTests' do
        inherit! :search_paths  # <-- excluded here
        shared_pods      # <-- added again here
      end
    end
    

    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.

    Login or Signup to reply.
  2. 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:

    post_install do |installer|
      remove_static_framework_duplicate_linkage({
            'FrameworkTarget1' => ['Firebase', 'FirebaseAnalytics', 'FirebaseCore', 'FirebaseCoreDiagnostics', 'FirebaseCoreDiagnosticsInterop', 'FirebaseInstanceID', 'FirebaseInstallations', 'GoogleAppMeasurement', 'GoogleDataTransport', 'GoogleDataTransportCCTSupport', 'GoogleUtilities'],
            'FrameworkTarget2' => ['GoogleMaps', 'GooglePlaces'],
      })
    
        installer.pods_project.targets.each do |target|
            target.build_configurations.each do |config|
                    config.build_settings['LD_NO_PIE'] = 'NO'
                    config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET'
            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
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search