🎁

Swift Package ManagerでObjective-Cを扱うには

2024/03/26に公開

昨今Objective-Cでアプリの新規開発をすることはないと思いますが、長年メンテナンスされてきたアプリのリファクタリングなどでObjective-C製のアプリを触ることがあるかもしれません。
また、現代のiOSアプリ開発において、パッケージ管理の方法もSwift Package Manager(以下、SPM)が主流となっています。
そこで、Objective-CコードをSPMで管理する際に注意すべきTipsを備忘録がてら記載します。

Swiftと混在できない

最初の注意点として、Objective-CファイルとSwiftファイルをひとつのターゲットとして管理することはできません。

例としてMixedProductというライブラリについて考えます。
MixedProductライブラリはSwiftObjective-Cで書かれた実装に依存していることを仮定します。

let package = Package(
  name: "MixedProduct",
  products: [
    .library(
      name: "MixedProduct",
      targets: ["MixedProduct"]
    )
  ],
  targets: [
    .target(
      name: "MixedProduct",
      dependencies: ["SwiftModule", "ObjCModule"]
    ),
    .target(
      name: "SwiftModule"
    ),
    .target(
      name: "ObjCModule"
    )
  ]
)

本来であれば、ひとつのターゲットとして管理したいところですが、ファイルの混在が許されていないため、別々のターゲットをそれぞれSwiftコードを格納しているSwiftModuleObjective-Cコードを格納しているObjCModuleとして定義しています。

MixedProductは、いずれのターゲットもdependenciesとして受け入れることができます。
もちろん、MixedProduct自体はSwiftでもObjective-Cでも構いませんが、これまで書いてきた通り、どちらかに統一しなければなりません。

上の例では母体となるMixedProductSwiftModuleObjCModuledependenciesとして定義していますが、状況によってはSwiftModuleObjCModuleを引き受けても構いません。
ただし、お互いのターゲットが参照し合うことはできず、下記のエラーでパッケージを読み込むことはできません。

cyclic dependency declaration found: SwiftModule -> ObjCModule -> SwiftModule;

includeディレクトリ

Objective-CのコードをSPMで管理する際にはヘッダーファイルをincludeディレクトリに格納する必要があります。

作成するディレクトリの配下にincludeディレクトリを作成する

Animalというパッケージを作ったとすると、その配下にincludeディレクトリを作成します。
そして、ヘッダーファイルはincludeディレクトリに格納し、実装ファイルはAnimalディレクトリ直下に配置していきます。

これは SPMがincludeという名前のディレクトリにあるヘッダーを自動的に公開するからです。

Swiftをヘッダーファイルにインポートする

Objective-CファイルではSwiftで記述されたパッケージを@import ModuleSwiftと記述することでインポートが可能です。しかし、ヘッダーファイルへのインポートがうまく動作しないことがあります。

そもそもまずは、

  1. Swiftパッケージ内のSwiftクラスが、@objc属性でマークされていること
  2. Swiftパッケージ内のSwiftクラスが、publicであること
  3. Bridging-Header.hの設定

を確認していただきたいのですが、それでもコンパイルできない場合にはxcconfigに下記を追加することで解決します。${YOUR_SWIFT_MODULE}を適宜変更して利用してください。

OTHER_SWIFT_FLAGS = -Xcc "-fmodule-map-file=${OBJROOT}/GeneratedModuleMaps-${PLATFORM_NAME}/${YOUR_SWIFT_MODULE}.modulemap"

これは明示的に特定のmodulemapを読み込むことで、リンクの解決を図りインポートが成功します。

参考: https://forums.swift.org/t/swift-cant-see-objc-methods-that-include-package-symbols/65732/9

おわり

以上です。
他にも遭遇したら追記します。
誰かの参考になれば幸いです。

GitHubで編集を提案

Discussion