8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ソースコード非公開のライブラリを、SPMとXcFrameworkで配布する話

Last updated at Posted at 2022-09-27

ソースコード非公開のライブラリを、SPMとXcFrameworkで配布する話

ソースコードを公開しないでライブラリを提供したいことはまれにあります。
このようなケースではiOSの場合、 XcFramework を使ってソースコード非公開のライブラリのパッケージを配布することができます。

Appleの基本情報は下記のURLにあります。

SPM

上記のAppleのドキュメントから引用すると、最終的に下記のようなPackage.swiftファイルを作成して配布することになります。

// swift-tools-version:5.3
import PackageDescription

let package = Package(
    name: "MyLibrary",
    platforms: [
        .macOS(.v10_14), .iOS(.v13), .tvOS(.v13)
    ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "MyLibrary",
            targets: ["MyLibrary", "SomeRemoteBinaryPackage", "SomeLocalBinaryPackage"])
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "MyLibrary"
        ),
        .binaryTarget(
            name: "SomeRemoteBinaryPackage",
            url: "https://url/to/some/remote/xcframework.zip",
            checksum: "The checksum of the ZIP archive that contains the XCFramework."
        ),
        .binaryTarget(
            name: "SomeLocalBinaryPackage",
            path: "path/to/some.xcframework"
        )
        .testTarget(
            name: "MyLibraryTests",
            dependencies: ["MyLibrary"]),
    ]
)

products に、 library を記述し、 targetsbinaryTarget として xcframework を配置するのが肝になります。

Xcode

プロジェクト

XcodeでXcFrameworkを作成することができます。
メニューから「File」→「New」→「Project...」を選ぶと下記のようなダイアログが表示されますので、 Framework を選択します。

アプリのProjectを作成する手順と同じ感じで進むと、XcFrameworkプロジェクトを作成することができます。

Build Settings

XcFrameworkを作成するためには、Build Settings を変更します。(後述のBuild Scriptで指定できる記事も見かけましたが、試してみたところエラーになるようでした)
Build Libraries for Distribution という項目がありますので、ここを YESに変更します。

このオプションについては、Build Settings Reference に記載があります。

Build Settings Referenceより引用

これによると、

ライブラリが配布用にビルドされていることを確認します。Swiftでは、ライブラリの進化とモジュールインターフェースファイルの生成のサポートが可能になります。

(By DeepL) ということらしいです。

ライブラリの進化(library evolution)」とは分かりにくいと言うか誤訳に近いと思いますが、つまり「バイナリ互換性」のことを言っています。ABI stabilityと言う表現の方がしっくりくる方も多いのではないでしょうか。Library Evolution in Swiftを読むと、まさにこのような記述されています。

Library Evolution in Swiftより引用

ソースコード

通常はこのあたりで 次項に記述する Build Script を走らせればXcFrameworkが完成するはずなのですが、特定の依存ライブラリがある場合は問題が起こります。

例えば XcFramework プロジェクト内で SwiftNIO を import しようとすると下記のようなwarningがでます。

warningなので、このままBuildできそうですが、実際にBuildして XcFramework として使用しようとすると

text
Module 'Framework B' was not compiled with library evolution support; using it means binary compatibility forFramework B' can not be guaranteed`

と怒られます。うまく動作しません。

実はXcodeでは、これをfixする提案が表示されます。

この @_implementationOnly とはなんでしょうか?🤔

どうやらソースコードが有効な依存ライブラリがある場合に、ABI stabilityが得られないので library evolution がサポートされないようです。
これを解消するために、@_implementationOnly を指定して依存ライブラリを XcFramework 内部に隠蔽して閉じ込め、安定して動作させるもののようです。

[2022/11/8追記]

@_implementationOnlyを利用する場合、importしているclassやprotocolを使っているコードは public にすることができません。
これは考えてみれば当たり前で、「依存ライブラリを内部に隠蔽して閉じ込め」ているので、public で公開することはできないのです。

Build Script

Build Scriptは下記のようなものになります。
実機向けのBuildと、エミュレータ向けのBuildを行い、その後それらから XcFramework を生成することで非常に使い勝手の良いバイナリーフレームワークを提供することができます。

#!/bin/sh

rm -r build
mkdir build

FRAMEWORK_NAME=HogeKit
PROJECT_NAME=HogeKit
DEVICE=device
SIMULATOR=simulator
DEVICE_FRAMEWORK=$DEVICE.xcarchive/Products/Library/Frameworks/$PROJECT_NAME.framework
SIMULATOR_FRAMEWORK=$SIMULATOR.xcarchive/Products/Library/Frameworks/$PROJECT_NAME.framework

# Build each XCFramework
xcodebuild archive -scheme $PROJECT_NAME -destination 'generic/platform=iOS' SKIP_INSTALL=NO -archivePath build/$DEVICE
xcodebuild archive -scheme $PROJECT_NAME -destination 'generic/platform=iOS Simulator' SKIP_INSTALL=NO -archivePath build/$SIMULATOR

# Combine XCFramework
xcodebuild -create-xcframework -framework build/$DEVICE_FRAMEWORK -framework build/$SIMULATOR_FRAMEWORK -output build/$FRAMEWORK_NAME.xcframework

名前空間

最後に名前空間の問題についてふれておきます。

XcFrameworkのPackage名を HogeKit として

import HogeKit

とする場合、例えば

enum HogeKit {
    static func makeXXX() {

    }
}

のような関数を用意することもあるかと思います。
しかし、これはなぜかうまく動作しません。

ライブラリ名と同一名称の enum / class などをモジュールのトップで定義すると、名前空間がうまく解決できずエラーになるようです。
これは、フルの関数名が、 HogeKit.HogeKit.makeXXX() となるためのようです。

enum Hoge {
    static func makeXXX() {

    }
}

と定義すれば、 Hoge.makeXXX() つまり、HogeKit.Hoge.makeXXX() は呼び出せるようになります。

参考

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?