Skip to content

Adding Objective C code to Swift framework (SPM, Carthage, Cocoa Pods)

Maciek Grzybowski edited this page Apr 10, 2020 · 1 revision

In PR #64 we added Objective-C module to our Swift SDK.


The goal was to:

  • write Objective-C code and use it from Swift;
  • do not expose Objective-C code to SDK users (make it internal);
  • make it work with SPM, Carthage and Cocoapods.

At the end, all was achieved with small issue on module public visibility. We managed to hide it from Xcode import autocompletion, but it can be still imported explicitly. Good enough 🥈.

How to do this:

First, no Xcode target is required ✋. A private module is defined only by module.private.modulemap.

All .m and .h files should be put inside single folder, DatadogPrivate/*.

The module.private.modulemap file is required. It should name the private module and list all the headers. It should be put inside DatadogPrivate/include/ (the include folder is required by Xcode 11.3, but not 11.4).

Next, depending on dependency manager the .modulemap and headers are discovered differently:

For Carthage, SWIFT_INCLUDE_PATHS build setting should be set to DatadogPrivate/** and all .h files should be added to Build Phases > Headers > Project build phase of Swift target;

For Cocoapods, the podspec should list source files, paths to preserve and SWIFT_INCLUDE_PATHS build setting:

  s.source_files = "Sources/Datadog/**/*.swift", "Datadog/DatadogPrivate/*.m"
  s.preserve_paths = "Datadog/DatadogPrivate/*.h", "Datadog/DatadogPrivate/include/*.modulemap"
  s.pod_target_xcconfig = { 
    "SWIFT_INCLUDE_PATHS" => "$(PODS_ROOT)/DatadogSDK/Datadog/DatadogPrivate/** $(PODS_TARGET_SRCROOT)/DatadogSDK/Datadog/DatadogPrivate/**"
  }

For Swift Package Manager, all header files must be put inside DatadogPrivate/include/. As we store them in DatadogPrivate/, we just added single SPMHeaders.h umbrella header with parent imports:

// SPMHeaders.h

#import "../Header1.h"
#import "../Header2.h"
#import "../Header3.h"

Then, in Package.swift we just defined the target and SPM does the rest by discovering the include/*.modulemap under given path:

.target(
   name: "_Datadog_Private",
   path: "Datadog/DatadogPrivate"
),