コード非公開のiOSライブラリをSwift Package Managerで配布する

SDK開発をしていると、**秘伝のソースコード自体は秘匿したいけど、SDKとして配布したいケース**があると思います。それも、定番のライブラリ管理ツールを使ってインストールできればディベロッパーフレンドリーですよね。
そんな制約のあるライブラリも Xcode 12 + XCFramework を使えば簡単に配布できます。

# 準備するもの

- ver.12以降のXcode
- Githubのパブリックリポジトリ（ライブラリ配布用）

# XCFramework とは
XCFrameworkは、Xcodeによって作成された配布可能なバイナリパッケージ。
`xcodebuild`コマンドで生成でき、わざわざ`lipo`をしなくてもユニバーサルフレームワーク化できる。
https://help.apple.com/xcode/mac/11.4/#/dev6f6ac218b

https://developer.apple.com/videos/play/wwdc2019/416/

<details><summary>ユニバーサルフレームワークとは...？</summary><div>
フレームワークはSimulatorやデバイス（それも各プラットフォーム）別にビルドする必要があり、それを`lipo`コマンドでまとめたりしたものをユニバーサルフレームワークと言います。`xcodebuild`コマンドによりXCFrameworkを生成することで、`lipo`せずにユニバーサル化を含めてよしなにビルドが完了します。
</div></details>

# XCFramework をつくる

今回は**iOS向け**に配布する想定でサンプルとして、今日のラッキーカラーを占う`TodayColor`フレームワーク、ラッキーナンバーを占う`TodayNumber`フレームワークをそれぞれ作ってみました。

![スクリーンショット 2021-03-02 13.21.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/672fdb2c-52ca-aecf-0c08-4814de426cba.png)

これをXCFrameworkにしていきます。

## XCArchive の作成
Simulatorと実機デバイス用にXCArchiveを作成します。
開発しているプロジェクトの構成に合わせてビルドコマンドは変えてください✌️😸
https://help.apple.com/xcode/mac/11.4/#/dev544efab96

各オプションやXCFramework対応については[こちらの@ArimeさんのQiita](https://qiita.com/Arime/items/ee9a41d849b473181728)がとても参考になりました🙇‍♂️ありがとうございました。

### Simulator 用の XCArchive を作成
```
$ xcodebuild \
'ENABLE_BITCODE=YES' \
'BITCODE_GENERATION_MODE=bitcode' \
'OTHER_CFLAGS=-fembed-bitcode' \
'BUILD_LIBRARY_FOR_DISTRIBUTION=YES' \
'SKIP_INSTALL=NO' \
archive \
-project Today.xcodeproj \
-scheme 'TodayColor' \
-destination 'generic/platform=iOS Simulator' \
-configuration 'Release' \
-archivePath 'build/TodayColor-iphonesimulator.xcarchive'
```

### 実機デバイス用の XCArchive を作成
```
$ xcodebuild \
'ENABLE_BITCODE=YES' \
'BITCODE_GENERATION_MODE=bitcode' \
'OTHER_CFLAGS=-fembed-bitcode' \
'BUILD_LIBRARY_FOR_DISTRIBUTION=YES' \
'SKIP_INSTALL=NO' \
archive \
-project Today.xcodeproj \
-scheme 'TodayColor' \
-destination 'generic/platform=iOS' \
-configuration 'Release' \
-archivePath 'build/TodayColor-iphoneos.xcarchive'
```

これで、Simulatorと実機デバイス、それぞれのXCArchiveが生成されました。

* TodayColor-iphonesimulator.xcarchive
* TodayColor-iphoneos.xcarchive

![スクリーンショット 2021-03-02 14.13.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/15502e3f-b101-dea1-1ff5-533e9e0f7126.png)

## XCFramework の作成
前述で作成したXCArchiveを一つにまとめていきます。

```
$ xcodebuild \
-create-xcframework \
-framework 'build/TodayColor-iphoneos.xcarchive/Products/Library/Frameworks/TodayColor.framework' \
-framework 'build/TodayColor-iphonesimulator.xcarchive/Products/Library/Frameworks/TodayColor.framework' \
-output 'build/TodayColor.xcframework'
```
はい、これでユニバーサルなXCFrameworkが完成です。

![スクリーンショット 2021-03-02 14.21.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/550d7a88-9395-149b-fba8-555878c638ba.png)

今回は`TodayColor`と`TodayNumber`の2つフレームワークを作ったので、同様の手順でもう一つXCFrameworkを作りました。

# Swift Package Manager で配布
**ここからは、開発用とは別の配布用リポジトリのディレクトリで作業していきます。**
**間違えてプライベートリポジトリのコードを`push`しないよう要注意🙀**
**`git`操作については既存リポジトリを利用するケースなどあると思いますので、適宜読替をお願いします。**

今回は配布用に、下記のGithubのパブリックリポジトリを作りました。

https://github.com/mesummery/TodaySDK

## Swift Package を作成
1. Xcodeを開く
2. File > New > Swift Package...
3. 名前をつけてSwift Packageを作成します

![スクリーンショット 2021-03-02 17.28.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/6962ebf6-ea1e-14c1-fad8-bded48e8ea17.png)

## `Package.swift`を編集
上記で作成した`.swiftpm/xcode/package.xcworkspace`を開き、`Package.swift`を編集していきます。

### `products` > `library`
サンプルではパッケージは`TodaySDK`一つですが

- ラッキーカラーを占いたい場合は`TodayColor`フレームワーク
- ラッキーナンバーを占いたい場合は`TodayNumber`フレームワーク

をディベロッパー側で選択できるよう、`products`の`library`を分けて配布します。

```swift
name: "TodaySDK",
products: [
    .library(
        name: "TodayColor",
        targets: ["TodayColor"]),
    .library(
        name: "TodayNumber",
        targets: ["TodayNumber"])
]
```

### `targets`
今回はバイナリをGithub上でホストするため、`binaryTarget`を指定します。
また、XCFramework は後述の作業で`Package.swift`と同じ階層に配置するので、下記のように指定をします。

```swift
targets: [
    .binaryTarget(name: "TodayColor",
                  path: "TodayColor.xcframework"),
    .binaryTarget(name: "TodayNumber",
                  path: "TodayNumber.xcframework"),
]
```

### `Package.swift`コード全体
https://github.com/mesummery/TodaySDK/blob/main/Package.swift

## XCFramework を配布用リポジトリに追加
[XCFrameworkの作成](#XCFrameworkの作成) 項目で作成したXCFrameworkを配布リポジトリに追加します。

```
$ cd path/to/repository/for/distribtion
$ cp -r path/to/.xcframework ./
```

## XCFramework と Swift Package を`git commit` & `push`
ここまで完了したら、作成した Swift Package と XCFramework を`git commit` & `push`します。

```
$ git commit . -m "Add Swift Package & XCFramework"
$ git push origin main
```

## タグ付け & リリース
```
$ git tag 1.0.0
$ git push origin 1.0.0
```

ののち、配布用リポジトリでリリースを行うだけで、Swift Package の公開は完了です👏👏😸

https://docs.github.com/ja/github/administering-a-repository/releasing-projects-on-github

# まとめ
Xcode11でSwiftPMが統合されて以降、iOS開発にSwiftPMを使う機会は以前より増えてきたな〜と思います。ライブラリ開発に手をつけるなら、今後、ますます定着するであろうSwiftPMサポートも進めていきたいですね！

SwiftPMのバイナリサポートについては、WWDCのビデオもあります。
8分弱なので気軽に見られておすすめです。

https://developer.apple.com/videos/play/wwdc2020/10147/

以上です :tada:
ありがとうございました。

# （番外編）SPMのフレームワークを試す
試しにフレームワークをインストールしてみましょう。
デモプロジェクトを作成し、File > Swift Packages > Add Package Dependency で`https://github.com/mesummery/TodaySDK`を入力しパッケージを追加します。
![スクリーンショット 2021-03-02 19.23.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/1243d187-0196-6464-10e9-fc354ff05931.png)
最新バージョンが取得できました。
![スクリーンショット 2021-03-02 19.23.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/04315b79-2394-8d97-aa6d-ea5b568af24d.png)
`TodayColor`と`TodayNumber`も分割できていますね。

```swift:ViewController.swift
import TodayColor

class ViewController: UIViewController {
    @IBOutlet weak var colorView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let color = TodayColor.tellMe()
        colorView.backgroundColor = color
    }
}
```
問題なく`import`できて、リンクも成功、ビルドもできました👏



|Today Color|Today Number|
|:-----------|:------------|
| <img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/68c940f9-622b-32d3-5a49-dcdd2dfe2ba0.png">| <img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/98613/31f60a7b-26a2-e623-f31b-474985c2080d.png">|