Skip to main content
  1. Posts/

既存アプリの実装をSwift Packageで再構成する方法①

今回は、既存アプリの実装をSwift Packageで再構成する方法をまとめます。xcodeprojの変更だけで済むので、比較的導入しやすいと思っています。xcodeprojファイルのコンフリクトに悩んでいるプロジェクトには、効果を発揮する気がしています。

手順 #

xcodeprojを開く #

まずは、いつものように、開発しているxcodeprojを開きます。

Packageを追加する #

File > New > Package を選択します。

パッケージの名前と保存先を選択します。保存先はプロジェクトのルートディレクトリが良いのではないでしょうか。

その際、ダイアログの下部にある「Add to:」の項目を、開発しているアプリのプロジェクトで指定します。「Group:」は、プロジェクト内の任意のグループ(今回はプロジェクトそのもの)を選択します。

パッケージが作成されると、このようなプロジェクトツリーになります。パッケージやSourcesやTestsのディレクトリも見えていますが、xcodeprojファイルに記録されているのはパッケージのみで、その中のファイルは記録されていません(=ファイルの表示順序は自動になる)。この時点で、コンフリクトが減りそうな予感がしてきます。

Dependenciesに追加する #

パッケージができたら、いつも通り、プロジェクトの依存に追加します。AppのターゲットやApp Extensionのターゲットなど、パッケージを必要とするターゲットに依存を追加します。

以上で準備は完了です。ここから先は、Swift Pakcageを開発する方法と同じです。すでにロジックをターゲットで分割していたプロジェクトであれば、ソースコードをパッケージに移動するだけで、ほとんどの作業が済みます。パッケージの基本的な構成を知る場合は、「Swift Packageの作り方① - 新規作成から実装まで」も参考になるかと思います。

パッケージの名前どうするか問題 #

まず初めに、パッケージは複数のターゲットを含むことができ、それらを単一のライブラリとして公開することもできるし、複数のライブラリとして公開することもできます。 下に、パッケージのPackage.swiftの例を挙げます。

let package = Package(
    name: "MyAppKit",
    platforms: [.iOS(.v16)],
    products: [
        .library(
            name: "Common",
            targets: ["Common"]),
        .library(
            name: "Repository",
            targets: ["Repository"]),
        .library(
            name: "Service",
            targets: ["Service"]),
    ],
    dependencies: [
    ],
    targets: [
        .target(
            name: "Common"
        ),
        .target(
            name: "Repository",
            dependencies: [.target(name: "Common")]
        ),
        .target(
            name: "Service",
            dependencies: [.target(name: "Common"), .target(name: "Repository")]
        )
    ]
)

このように、ターゲット同士の依存関係も作ることができるようになっているので、アプリに必要なモジュールを1つのパッケージにまとめることができます。

このような都合から、私の場合、これを書いている現在は「〇〇Kit」(例: MyAppKit)という名前で作ることが多くなっています。 Kitさえ見ればアプリを作るのに必要な機能を網羅的に知ることができるので、Kitで良さそうだなと思ってこの名前にしています。

……とはいえ、名前はxcodeprojを作るよりは気楽に決められます。 仮に、後から変更することになっても、せいぜいディレクトリの名前とPackage.swiftを修正し、importする各所を変更する程度で済むからです。 パッケージを単一のライブラリにするなら「〇〇Feature」や「Repository」でも良さそうですし、複数のライブラリにするなら「MyAppKit」もありだと思っています。

ローカライゼーションやリソースどうするか問題 #

Stringsファイルや、それらを含む.lprojディレクトリなどは、リソースになります。 それらを保存するため、パッケージに Sources > [ターゲット名のディレクトリ] > Resources のようなディレクトリを作成し、その中にファイルを置きます。

その上で、Package.swiftを編集し、ターゲットにリソースのディレクトリを指定します。

targets: [
    .target(
        name: "Common",
        resources: [.process("Resources")]
    ),
]

ローカライゼーションが必要な場合は、パッケージのデフォルトの言語を defaultLocalization に指定します。

let package = Package(
    name: "MyAppKit",
    defaultLocalization: "en",
    platforms: [.iOS(.v16)],

これで、リソースやローカライゼーションに対応することができます。

既存アプリにもパッケージを導入して、プロジェクトを再構成しよう #

今回は、既存のアプリにパッケージを導入して、設計を再構築する初歩をまとめました。 以前まとめた「Swift Packageの作り方① - 新規作成から実装まで」では、Packageとxcodeprojをxcworkspaceでまとめる方法を採っており、既存アプリへ導入するにはちょっと腰が重い方法になっていました(workspaceに変更する旨を周知したり、CIの設定を変更する必要がある)。しかし、今回の方法であれば、xcodeprojをそのまま使用し続けられるので、採用しやすいかなと思っています。