Swift Package Manager with a Mixed Swift and Objective-C Project (part 2/2)

Update: Dec 18, 2021

  1. Separating targets for Swift and ObjC source code. ie. How to structure the package manifest (Package.swift) to provide a Swift target that depends on an ObjC target.
  2. Resolving public interfaces so that APIs are available from ObjC projects.
  3. Passing through an ObjC interface so that it’s available to Swift users
  4. Issues with diverging module import syntax. ex: # import <foo/bar.h> vs #import “bar.h"
  1. Fewer sources of truth to maintain. This is huge. Every way of building your library is a vector for issues. Fewer vector, fewer issues.
  2. No need to have separate targets for Swift and ObjC source code. Resolving the underlying modules is handled automatically by Xcode and is based on the project file you already maintain.
  3. No need to restructure your project or use hacky workarounds like symlinks in order to expose your ObjC interfaces to your Swift code.
  4. No need to re-export nested ObjC modules to end users.
  5. No need to modify existing imports statements to support diverting syntax.
  6. No need to have additional CI jobs related to building from a Package file.
  7. No need to duplicate QA efforts aside from making sure that the XCFramework is available. If the framework can be obtained from the Package, you can safely assume that it will behave the same as if it were vended from other sources.
  • Generate project files on-demand using Xcodegen or a similar tool.
  • Use the project to create XCFrameworks.
  • Distribute those frameworks via a repository that contains ONLY a Package.swift file.

BACKGROUND

  • How to structure your package manifest (Package.swift) to provide a Swift target that depends on an ObjC target
  • How to resolve your public interface so that your APIs are available from ObjC projects
  • How to pass through your ObjC interface so that it’s available to Swift users
  • How to avoid the trap of fully qualified module import syntax. ie. issues surrounding# import <foo/bar.h> vs #import “bar.h"

PACKAGE MANIFEST

.target(
name: "ModuleX-ObjC", // 1
dependencies: [], // 2
path: "ModuleX/", // 3
exclude: ["SwiftSources"], // 4
cSettings: [
.headerSearchPath("Internal"), // 5
]
),
.target(
name: "ModuleX", // 6
dependencies: ["ModuleX-ObjC"], // 7
path: "SwiftSources" // 8
),
  1. First you need to create a target and name it in a way that distinguishes it as your ObjC dependency.
  2. If you had any dependencies for your ObjC library you would put them here
  3. This assumes that all of your ObjC files are in a directory named ‘ModuleX/’. If all of your source files are in a directory named ‘src’ or ‘sources’ then this argument can be omitted as SPM will automatically find them.
  4. This relies on the important assumption that all of your Swift source files are colocated in their own directory(s). In this example we assume that they are in a directory named “SwiftSources”. Currently there is not a way to exclude files based on extension. 😢
  5. Header search paths are for files within ModuleX to resolve themselves so that we don’t get cannot find <module-name/module-name.h> type errors. This is not the same as exposing them as a publicly available interface (we will cover how to do that shortly). This example assumes you have your internal headers in a directory named “Internal”.
  6. This is the name that will be used to import your Swift module into projects.
  7. This is the dependency on your ObjC module.
  8. Again, this assumes that your Swift source files are colocated in a directory named “SwiftSources”.

PUBLIC INTERFACES

Move all of your header files into the include directory.

Have a separate header file that you only use for your Swift package.

// ModuleX.h#import "../MyFeature/SomeClass.h"

Use Symlinks

EXPOSING YOUR OBJC TO SWIFT USERS

@_exported import ModuleX-Objc

HEADER TRAPS

#if SWIFT_PACKAGE
#import "SomeHeader.h"
#else
#import <MyModule/SomeHeader.h>
#endif
#if SOME_BUILD_SYSTEM
#import <MyModule/SomeHeader.h>
#else
#import "SomeHeader.h"
#endif

CONCLUSION

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

How to Sort an Array of 0s, 1s, and 2s in Java

Transaction Management in Cassandra

KNOLDUS-advt-sticker

Efficient & Compliant Business with Trusted Reference Data Management (RDM)

How to collect PIGI tokens?

XBinary: Extended Binary Format Support for Mac OS X

Synchronizer Token Pattern (STP)

A case for Agile

Over 36,000 KSM Addresses Reserved for Bifrost Kusama Slot Auction | Weekly Report 54

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Joe Susnick

Joe Susnick

More from Medium

[Codility] Triangle (Swift)

Singleton in Swift New

Swift 5.6: Combining Logical Operators

Dependency Injection in iOS Applications Part 2 — Container based DI