CocoaPods: How to Deal with Device-Only Dependencies

This week, I faced a problem I had been postponing for a long time.

Since Apple released its first MacBook with M1 processors, developers have had to start thinking about creating simulator frameworks with arm64 architecture.

That was the death sentence for fat binaries, as a binary cannot contain two identical architectures: an arm64 architecture for devices and an arm64 architecture for simulators.

From this point, we started working with Xcframeworks: a package containing two or more platforms within itself.

So a new problem arose: how to link device-only libraries with simulators using CocoaPods?

Before the arm64 architecture for simulators, when we tried to link a device-only library in a build to the simulator, the linker simply ignored the library because it didn’t contain any valid architecture, generating a warning, and everything was fine. But now that the arm64 architecture has become a valid architecture for simulators, the linker, when trying to link the build with the device-only library, started to display the following error:

#blindPeople: The image shows a linker error with the message: “ld: building for iOS Simulator, but linking in dylib built for iOS for architecture arm64.”

So we came to the problem: how to tell CocoaPods that the library I’m trying to import should be used only for devices and not for simulators? We can specify configurations for a Pod, but not a platform for a configuration.

The Solution: A Custom Podspec

Creating a custom podspec is not a difficult task, but finding the right parameters for the solution you want can be a challenge.

In this case, we need to remove the vendored_frameworks library:

#blindPeople: The image shows a podspec file with the vendored_frameworks property selected, with the value MyDeviceOnlyFramework.framework.

When specified as a vendored framework, CocoaPods already includes the library wherever it is needed, from search path frameworks to the linker itself.

In this case, we will do the process manually, as we want a very specific configuration.

In order for CocoaPods to download and keep our library as a dependency, we need to specify a path that will be preserved during the Pod installation (in this case, our framework):

 s.preserve_paths = "MyDeviceOnlyFramework.framework/*"

Next, we need to tell CocoaPods all the additional configuration that the library needs to be used by our main project. For this, we include the xcconfig property, passing the necessary information to the configuration file that will be generated.

 s.xcconfig = {
        'FRAMEWORK_SEARCH_PATH[sdk=iphoneos*]' => '$(inherited) "$(PODS_ROOT)/MyDeviceOnlyFramework"',
        'OTHERCFLAGS[sdk=iphoneos*]' => '$(inherited) -iframework "$(PODS_ROOT)/MyDeviceOnlyFramework"',
        'OTHER_LDFLAGS[sdk=iphoneos*]' => '$(inherited) -framework MyDeviceOnlyFramework'
    }

Note that we use the iphoneos* variant for our settings. This variant allows us to set values only for devices and not for simulators.

When it’s done, just upload your custom podspec to your spec repository or store it in a location that CocoaPods can access, and run pod install in your project (don’t forget the —repo-update to update your dependencies if necessary).

The final solution

After pod install, your dependency will display its settings as follows in Xcode:

#blindPeople: The image shows the Other Linker Flags configuration block with the config debug and release, with the MyDeviceOnlyFramework dependency set only for the platform Any iOS SDK.

In the end, your podspec will look like this:

Pod::Spec.new do |s|

    s.name          = "MyDeviceOnlyFramework"
    s.version       = "1.0.0"
    s.summary       = "This is a Device Only Framework"

    s.description   = <<-DESC
                        Device Only Framework Podspec Example.
                      DESC
    
    s.homepage      = "http://mywebpage.xpto/MyDeviceOnlyFramework"
    s.license       = { :type => "Copyright", :file => "LICENSE" }

    s.author        = { "Andre Salla" => "contato@andresalla.com" }
    s.platform      = :ios, "10.0"

    s.source        = { :git => "http://mygitrepo.xpto/MyDeviceOnlyFramework.git", :tag => "1.0.0"}

    s.preserve_paths = "MyDeviceOnlyFramework.framework/*"

    s.xcconfig = {
        'FRAMEWORK_SEARCH_PATH[sdk=iphoneos*]' => '$(inherited) "$(PODS_ROOT)/MyDeviceOnlyFramework"',
        'OTHERCFLAGS[sdk=iphoneos*]' => '$(inherited) -iframework "$(PODS_ROOT)/MyDeviceOnlyFramework"',
        'OTHER_LDFLAGS[sdk=iphoneos*]' => '$(inherited) -framework MyDeviceOnlyFramework'
    }

end

Special thanks

I want to thank August Jaenicke, as his post helped me get out of this complicated situation and showed me the light at the end of the tunnel.

Link: https://blog.carbonfive.com/cocoapods-for-device-only-ios-libraries/