💡

xcframeworkが依存するモジュールを隠蔽する

2022/12/16に公開

結構困ったので雑メモ

背景

サードパティSDKの開発をしていると内部の依存を隠蔽したいときがある。
例えば、InternalなAPIを含むモジュール(例えば、社内でのみ利用されてるモジュールやマルチモジュール化によって切り出されたモジュール)はSDKの利用者に参照されたくない。

問題

しかし、Swift Package Managerに挙げられるような一般的なPackage Managerでは依存しているモジュールのバージョンの解決やシンボルの重複を避けるために、トップレベルのモジュールが依存の解決を行う。
そのため、InternalなAPIだとしても、単純にimportした場合に下記のようなエラーが発生する。

このプロジェクトでは下記のような依存関係になっている。

ClientApp(アプリ) -> PublicFramework(xcframework) -> InternalPackage(swift package)

PublicFrameworkがInternalPackageに依存している事をClientAppが知っているが、InternalPackageのヘッダーが見つけられず、エラーが起きていると思われる。
そのため、InternalPackageをClientAppの依存に加えればコンパイルが成功するが、この方法はモチベーションに反する。

解決

InternalPackageをimportする際に下記のように@_implementationOnlyというAttributeをつける。

- import InternalPackage
+ @_implementationOnly import InternalPackage

@_implementationOnly

@_implementationOnlyはapple/swiftで下記のように説明されているAttriubte。

Used to mark an imported module as an implementation detail.
This prevents types from that module being exposed in API
(types of public functions, constraints in public extension etc.)
and ABI (usage in @inlinable code).

https://github.com/apple/swift/blob/abfdc1b96ce55cc4bc7461ff57ae34a61dba9aed/docs/ReferenceGuides/UnderscoredAttributes.md?plain=1#L468-L473

firebase-ios-sdkでもinternalなAPIを隠すために使われている。

// Avoids exposing internal FirebaseCore APIs to Swift users.
@_implementationOnly import FirebaseCoreExtension

https://github.com/firebase/firebase-ios-sdk/blob/8a0bb35f019d062d7aefd118cd0d3cf2482ef293/FirebaseStorage/Sources/StorageComponent.swift#L20-L21

注意

おそらく、下記のような依存関係になった時にシンボルの重複が起きる。

ClientApp(アプリ) -> PublicFramework(xcframework)
ClientApp(アプリ) -> InternalPackage(swift package)
PublicFramework(xcframework) -> InternalPackage(swift package)

感想

  • xcframeworkの生成方法の工夫などで@_implementationOnlyを使わずに済むなら、そうしたい。
  • このあたり不明点多いので問題の解釈が間違ってるかも知れない。

参考文献

Discussion