初めての Swift Package ライブラリーハンズオン

# 注

本記事は[技育祭2022春](https://talent.supporterz.jp/geeksai/2022spring/)の勉強会セッションで行われるハンズオンのための資料です。

# 前提条件

- Xcode がインストールされています
- GitHub（GitLab 等でも可）のアカウントを持っています
- 上記アカウントへのプッシュ可能なローカル環境が構築されています

# ゴール

- おみくじを引くライブラリーを作り、Swift Package として GitHub で配布します

    ![スクリーンショット 2022-03-18 2.08.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/22b44606-22c2-75b3-084e-9cfd042a9e6a.png)

- 配布したライブラリーが正しく動作できるか、検証アプリで動作確認します

    ![スクリーンショット 2022-03-18 2.05.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/7eb22ee1-784f-b248-678a-3fc668bef38c.png)

# 下準備

1. ライブラリー名を決めます

    例：`Omikuji`

1. ライブラリーの置き場を決めます

    例：`~/Documents/Omikuji`（=`/Users/MyAccount/Documents/Omikuji`）

# Swift Package を作成

1. Xcode を開き、新規プロジェクトを作ります

    ![スクリーンショット 2022-03-17 1.18.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/ee70dad8-92a0-b85f-b17e-c9fc8df6990e.png)

1. Multiplatform タブから Swift Package プロジェクトを選び、次に進みます

    ![スクリーンショット 2022-03-17 1.19.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/4d680e7a-cb85-191d-f299-152eea83e10b.png)

1. 先ほど決めた置き場とライブラリー名で保存し、Create Git repository on my Mac オプションにチェックを入れます

    ![スクリーンショット 2022-03-17 1.26.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/5de92b67-0ccd-8f25-9df3-0f5e4316919d.png)


1. もし git の GUI クライアント（例：SourceTree や Fork）を利用しているなら、該当パスをクライアントに追加します（しなくてもターミナルから git 使えるので問題ありません、好みに応じて使い分けてください）

    ![スクリーンショット 2022-03-17 1.28.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/cdbdb315-abae-86f8-129c-4a60880fa0c7.png)

1. 初期状態で作られたものを確認します

    Xcode に戻ると、Omikuji/Sources/Omikuji/Omikuji.swift というファイルがあるので、それをクリックすると初期状態で下記のようなコードがすでに書かれていることが確認できます：

    ```swift:Omikuji.swift
    public struct Omikuji {
        public private(set) var text = "Hello, World!"

        public init() {
        }
    }
    ```

# 軽い動作確認

1. 複数の Xcode で同じプロジェクトを開けないため、一旦ライブラリーを開いてる Xcode を閉じます

1. ライブラリーを利用するアプリプロジェクトを作成します

    Xcode で新規 Multiplatform App プロジェクトを、ライブラリーの親フォルダーに作成します
    例：`~/Documents/OmikujiApp`
    ![スクリーンショット 2022-03-18 1.11.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/e2490b44-1186-1ba7-55c9-b5a3265bc803.png)

1. プロジェクト設定の Package Dependencies タブから、先ほど作ったライブラリーをローカルパスで追加します

    ![スクリーンショット 2022-03-18 1.11.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/706fbbc4-44dd-2577-6f61-a584a7abf4f9.png)

1. iOS もしくは macOS アプリターゲット（動作確認したいターゲットで OK です；両方入れても可）設定の General タブから、先ほど作ったライブラリーを Frameworks, Libraries, and Embedded Content に追加

    ![スクリーンショット 2022-03-18 1.13.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/8d457190-3d2f-1179-d273-ae79e378414a.png)

1. Shared の `ContentView.swift` を開いて、ライブラリーの出力を確認します

    先頭に `import Omikuji` を追加し、`Text("Hello, world!")` の部分を `Text(Omikuji().text)` で置き換え、プレビュー画面が `Hello, World!` に変わったかどうか（小文字の `w` から大文字の `W` に変わったかどうか）を確認します

    ```diff_swift:ContentView.swift
    import SwiftUI
    +import Omikuji

    struct ContentView: View {
        var body: some View {
    -        Text("Hello, world")
    +        Text(Omikuji().text)
                .padding()
        }
    }
    ```
    ![スクリーンショット 2022-03-18 1.17.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/14dd3a19-b26a-67bd-c173-459aacac6aa9.png)

# ライブラリーの機能を実装

1. 動作確認アプリから直接 Omikuji パッケージの編集ができるので、そのまま Packages/Omikuji/Sources/Omikuji を開きます

1. せっかく Swift は日本語プログラミングに対応しているので、日本語で実装しようと思います。というわけで既存の `Omikuji.swift` を削除し、新たに `おみくじ.swift` ファイルを作ります

    ![スクリーンショット 2022-03-18 1.22.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/2b5df9d7-09eb-a1ae-8905-9ad5c1bce336.png)

1. おみくじですので、`大吉` `中吉` `小吉` `吉` `末吉` `凶` `大凶` の `enum` を作ります

    ライブラリー外のモジュール、すなわち検証アプリやライブラリーを利用するアプリで利用するので、`public` で作る必要があります
    ```swift:おみくじ.swift
    public enum おみくじ {
        case 大吉
        case 中吉
        case 小吉
        case 吉
        case 末吉
        case 凶
        case 大凶
    }
    ```

1. おみくじを引きたいので、`引く()` という静的メソッドを作ります

    Swift には全ての case を配列で羅列してくれるためのプロトコル `CaseIterable` と、配列からランダムな要素を取り出すためのメソッド `randomElement()` があるので、これらを活用して実装します
    ```diff_swift:おみくじ.swift
    -public enum おみくじ {
    +public enum おみくじ: CaseIterable {
        case 大吉
        case 中吉
        case 小吉
        case 吉
        case 末吉
        case 凶
        case 大凶
    }
    +
    +extension おみくじ {
    +
    +    public static func 引く() -> Self {
    +        allCases.randomElement()!
    +    }
    +
    +}
    ```

1. 画面で引いた結果を表示したいので、`おみくじ` を `String` として、`CustomStringConvertible` に適合します

    ```diff_swift:おみくじ.swift
    -public enum おみくじ: CaseIterable {
    +public enum おみくじ: String, CaseIterable {
        // ...
    }
    // ...
    +extension おみくじ: CustomStringConvertible {
    +
    +    public var description: String {
    +        rawValue
    +    }
    +
    +}
    ```

1. 検証アプリの `ContentView.swift` に戻り、今の実装を確認してみます

    `Text` の内容を `おみくじ.引く().description` で置き換えれば OK です
    ```diff_swift:ContentView.swift
    struct ContentView: View {
        var body: some View {
    -        Text(Omikuji().text)
    +        Text(おみくじ.引く().description)
                .padding()
        }
    }
    ```
    ![スクリーンショット 2022-03-18 1.43.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/75585e5a-aa59-32e4-ce85-caa4a94725f6.png)

1. 動作が無事確認できたら git にコミットします

    ターミナルから直接使っている場合は
    ```zsh
    % git add .
    % git commit -m "初コミット"
    ```
    のように全ての差分をステージングし、`初コミット`などのコミットメッセージでコミットすれば OK です。コミットメッセージの内容は自由です。
    また SourceTree や Fork を使っている場合は、それらのツールの使い方に従って全ての差分をコミットします。

# OSS として公開

1. GitHub（もしくは GitLab とかでも問題ありません）で新しい空リポジトリーを作成します

    例： `https://github.com/my-account/omikuji`

1. 作られたリモートリポジトリーをローカルの git リポジトリーに設定します

    ターミナルから git を利用している場合は
    ```zsh
    % git remote add origin https://github.com/my-account/omikuji
    ```
    SourceTree などの GUI アプリの場合はアプリの使い方に従って設定します

1. リポジトリーを GitHub に Push します

    ターミナルから git を利用している場合は
    ```zsh
    % git push --set-upstream origin main
    ```
    SourceTree などの GUI アプリの場合はアプリの使い方に従って設定します

1. GitHub から最新の main ブランチをリリースします

    リリースバージョンは[セマンティックバージョニング](https://semver.org/lang/ja/)をお勧めします。それに従うと今回は初期開発リリースとしてバージョン 0.1.0 でリリースします。この際のタグは `v0.1.0` とします
    ![スクリーンショット 2022-03-18 1.55.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/05944eed-9d9a-6bea-53a5-e8cfcbc423a9.png)

1. 【オプショナル】SNS とかで自分が作ったライブラリーを宣伝します

    そりゃあみんなに使ってほしいですよね！？

# 作られたライブラリーを実際に利用

1. 上に作った動作確認用のプロジェクトを開き、確認のためのローカルライブラリーの設定を削除

    プロジェクト配下の `Packages` を丸ごと削除（元のデータを消したいわけではないので、Remove Reference で削除します）
    ![スクリーンショット 2022-03-18 1.59.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/2d5fffcf-8ec1-07f3-b514-829b2e006f78.png)

1. 再びプロジェクト設定の Package Dependencies タブから、公開 URL を入れてライブラリーを導入します

    ![スクリーンショット 2022-03-18 2.03.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/c0f7a5d8-6bfb-c61e-6b33-da78d3553468.png)

1. もう一回 `ContentView.swift` を開いて動作確認します

    この際プレビューで表示されているおみくじの結果がおそらく変わります。もし変わらなかったら何回か別のファイルに切り替えてまた `ContentView.swift` ファイルに戻ってみてください。その度にプレビューが再生成され、新しいおみくじ結果が表示されるはずです
    ![スクリーンショット 2022-03-18 2.05.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63998/bc8002eb-ae5b-20f3-fac8-001a4630fcce.png)

# 以上

これであなたも自分の Swift Package ライブラリーの配布ができるようになりました！

また、今後は更に README.md を充実させてみるなどで、より人に使ってもらいやすいライブラリーに仕上げてみてもいいと思いますよ！

# 早くできた人へ

既にある程度の Swift 経験がある人なら今回のハンズオンはとてもスムーズに早くできちゃったかもしれません。というわけでそんなあなたに、とっておきな追加課題を出しましょう。実施は任意です。

1. 今回のおみくじは完全にランダムであり、（乱数アルゴリズムのばらつきを無視すれば）どの結果でも同じ確率が出てきます。ところが実際に神社やお寺で引いたおみくじは（場所にもよりますが）いい結果が出る確率が高いです。この `引く` メソッドもそうなるように調整してみましょう。

1. いい結果が出なかった人が、次にもう一回引きたいと思う人もいます。そんな時にもし同じ結果が出てしまうとちょっとゲンナリしますよね。そんなわがままな人のために、前回とは必ず違う結果が出る `再度引く` メソッドを作ってみましょう。その時、前回と違う結果を回避するためにどんな方法があるか（例えば前回の結果を引数からもらうか、それとも `引く` メソッドと同じく引数なしでどうにかするか；そして前回の結果を知った上でそれをどのように回避するかなど）も自由に考えて、それぞれのアプローチのメリットデメリットも考えてみましょう。

1. 上記のブラッシュアップをリリースしてみましょう。その時、本記事にも既に出てた「セマンティックバージョニング」でバージョン番号を付ける際にはどのバージョンにすべきかも考えておきましょう。そしてリリースノートも忘れずに書いておきましょう。