複数OSに対応したSwift oss library作成チュートリアル 

## はじめに

iOS，macOS両対応のSwiftライブラリを作りたいと思い立ったのですが，それってどうやって作れば良いのだろう。
今回はRxSwiftに依存したものを作りたかったので，[RxBluetoothKit](https://github.com/Polidea/RxBluetoothKit)を紐解きつつ作成手順を記します。
結構長いので覚悟してついてきて下さい。

## 環境

- macOS High Sierra 10.13.6
- Xcode 9.4.1

## 本記事完了時点でのディレクトリ

2018/08/23現在のRxBluetoothKit masterの様子 -> [69c1d18](https://github.com/Polidea/RxBluetoothKit/tree/6318aa28665e94377ceb806f66bfbef771666207)
主要ディレクトリおよびファイルを抜粋したものです。ここまでを目指します。

```
ExampleApp/
　├ ExampleApp.xcodeproj/
　├ ExampleApp.xcworkspace/
　├ ExampleApp/
　└ Podfile
RxBluetoothKit.xcodeproj/
Source/
Tests/
　└ Info.plist
docs/
　└ index.html
scripts/
　└ all-tests.sh
.gitignore
.jazzy.yaml
.swift-version
.swiftlint.yml
.travis.yml
Cartfile
Cartfile.private
Cartfile.resolved
LICENSE
Package.swift
README.md
RxBluetoothKit.podspec
```

## ディレクトリの作成

最初にgitから作成します。今回は中~大規模を想定していますので，[Git-flow](https://qiita.com/KosukeSone/items/514dd24828b485c69a05)を利用することにします。

```
$ mkdir <foo>
$ cd <foo>
$ git init
$ git flow init
```

#### ここまでのディレクトリ構成

以降は`.git`は省略します。

```
.git
```

#### 参考

- [Git-flowって何？](https://qiita.com/KosukeSone/items/514dd24828b485c69a05)

## ソースファイルの作成

[Swift Package Manager](https://github.com/apple/swift-package-manager)を利用してプロジェクトを作成します。今回はライブラリ形式で作成するので，`--type library`のオプションを付けています。

```
$ swift package init --type library
```

#### ここまでのディレクトリ構成

```
Source/
　└ <foo>/
　   └ test.swift
Tests/
　├ <foo>Tests/
　│　├ <foo>Tests.swift
　│　└ XCTestManifests.swift
　└ LinuxMain.swift
.gitignore
Package.swift
README.md
```

一気にそれっぽくなってきました。
この辺りで`.gitignore`や`README.md`の整理をすると良いでしょう。`LICENSE`の追加をしても良いです。
`LinuxMain.swift` `XCTestManifests.swift` は不要なので削除します。

#### 参考

- [Create a library package | apple/swift-package-manager - GitHub](https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md#create-a-library-package)
- [github/gitignore - GitHub](https://github.com/github/gitignore)

## xcodeproj の作成

```
$ swift build
$ swift package generate-xcodeproj
```

ここでようやくXcodeから開くことができるようになります。
Xcodeで開くと`Sources`と`Tests`が参照無しのGroup(Group without Folder)となっているのでここで直しておきましょう。

<img width="269" alt="ss 2018-08-23 at 21.38.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/660e77f4-18f5-53e9-7283-91ffd00beb85.png">

#### ここまでのディレクトリ構成

```
<foo>.xcodeproj/
Source/
　└ <foo>/
　　　└ test.swift
Tests/
　└ <foo>Tests/
　　　└ <foo>Tests.swift
.build/
.gitignore
LICENSE
Package.swift
README.md
```

#### 参考

- [PackageDescription API Version 4 | apple/swift-package-manager - GitHub](https://github.com/apple/swift-package-manager/blob/master/Documentation/PackageDescriptionV4.md)

#### 参考

- [自前のライブラリをCocoaPodsで管理するメモ](https://qiita.com/somtd/items/9386e2185adfff4c54bc)

## TARGETの作成

Xcodeでの作業です。自動生成されたTARGETを削除し，必要なものを作成します。
今回はiOS (Cocoa Touch Framework) とmacOS (Cocoa Framework) を用意しました。

![ss 2018-08-14 at 10.35.37 copy.png](https://qiita-image-store.s3.amazonaws.com/0/195913/582dd6cb-d462-6e0c-1420-05a7f67021c1.png)

ここでいくつかのファイルが自動生成されます。
OS毎に共通した`Info.plist`があれば良いので，Framework用とTest用で1つずつ用意します。テストファイルも共通化しましょう。それ以外のファイルは削除します。

#### Info.plistの移動

- Framework用のInfo.plistの移動

Framework用のInfo.plistはSource以下に配置し，
一旦`Remove Reference`で参照を破棄します。

<img width="1135" alt="ss 2018-08-23 at 21.40.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/038a9c21-203a-aa44-ad9c-08372498cbfd.png">

その後Xcode上のプロジェクト直下で`New Group without Folder`として`Supporting Files`を作成し，Info.plistを参照します。

<img width="1141" alt="ss 2018-08-23 at 21.46.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/593d81c6-28ec-ae3c-a733-94681c24c0a0.png">

この時TARGETSの`Build Phases / Copy Bundle Resources`にInfo.plistが存在すると正常にビルドできないため削除します。

<img width="1512" alt="ss 2018-08-23 at 21.46.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/01b97b9e-1ff0-fb90-7b4b-7ab6431355b3.png">

ここで間違っている場合はTARGETSのIdentifyに`Choose Info.plist File...`が表示されています。選択して直してあげましょう。

<img width="862" alt="ss 2018-08-23 at 22.04.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/3127afde-f6ae-8981-9f06-8860f9c18e21.png">

- Test用のInfo.plistの移動

こちらはそのままTests以下にTest用のInfo.plistを配置します。

しかし，テストの方はTARGETSの`Build Settings / Packaging / Info.plist File`をマニュアルで変更する必要があります。

<img width="866" alt="ss 2018-08-23 at 22.11.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/41b9cbad-85d6-248b-247e-3e30bab4a4c8.png">


#### ここまでのディレクトリ構成

<img width="268" alt="ss 2018-08-23 at 21.54.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/2e145c78-8bfe-08c2-158e-eeb84ab25c7e.png">

```
<foo>.xcodeproj/
Source/
　├ <foo>/
　│　└ <foo>.swift
　└ Info.plist
Tests/
　├ <foo>Tests/
　│　└ <foo>Tests.swift
　└ Info.plist
.build/
.gitignore
LICENSE
Package.swift
README.md
```

## Schemeの設定

XcodeのTARGETSを変更したのでSchemeが崩れています。調整しましょう。
`Manage Schemes...`から不要なSchemeを削除し，残ったものの`Shared`にチェックを付けましょう。後にTravis CIで参照する際に必要となります。

<img width="404" alt="ss 2018-08-23 at 22.01.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/4ee1657a-5a8e-948a-b618-2a2be67eac72.png">

<img width="767" alt="ss 2018-08-23 at 22.02.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/9b0fd588-62f5-cad6-7146-8cd1efe66df7.png">

#### TARGET Deviceの調整

ターゲットのデバイスも調整しましょう。現在は`Cocoa Touch Framework`で作成してもSchemeには反映されていません。
変更したいTARGETの`Build Settings / Architectures / Supported Platforms`を変更する必要があります。

<img width="637" alt="ss 2018-08-23 at 22.16.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/e64276fe-7f98-d37b-9efa-a1751e03b4e1.png">

<img width="866" alt="ss 2018-08-23 at 22.21.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/08fe6edc-4999-4ba9-41e2-2ee224bd9f32.png">

#### Test実行

ここでビルドおよびテストを実行してみましょう。ビルドは通りますが，テストは通らないと思います。
原因はテスト共通化のために追加したTARGET名が`@testable import`と異なることで発生しています。修正しましょう。

**Framework**のTARGETの`Build Settings / Packaging / Product Name`を共通する名前に変更しましょう(TestのProduct Nameの変更は不要です)。
テストが通るかと思います。

<img width="864" alt="ss 2018-08-23 at 22.33.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/c15c8754-3176-1ec5-3137-99cf5a2f8374.png">

#### 参考

- [XcodeのSchemeでBuildするDeviceを指定する方法](https://qiita.com/YutoMizutani/items/f76ae1c2e0446aeadeea)

## 依存ライブラリの組み込み

Carthageを利用します。Homebrewは以後も利用しますので，利用していない方は[【Swift】Carthage導入手順](https://qiita.com/ShinokiRyosei/items/9b856ebdec5379b6c631)等を元に導入しましょう。

```
$ brew install carthage
$ touch Cartfile
```

Vim等で編集します。今回はRxSwiftに依存させます。

```:Cartfile
github "ReactiveX/RxSwift" ~> 4.0
```

インストールします。

```
$ carthage update
```

`New Group without Folder`を選択し，`Frameworks`という名で作成します。
Framework用にProject直下に1つ，Test用に`Tests/`以下に1つ用意しましょう。
RxSwiftは各OS毎にBuild fileが分かれています。`Frameworks`以下に`iOS` `macOS`というGroup without Folderを作成しましょう。
RxSwiftでは`RxTest` `RxBlocking`がテスト用ですので必要に応じて取り込みます。

<img width="1396" alt="ss 2018-08-23 at 23.57.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/5e5653e2-df0c-e14f-04ca-7957a73f358f.png">

<img width="1401" alt="ss 2018-08-24 at 0.03.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/3abf91f6-a2ed-06a9-2103-58428539041d.png">

適切に設定できた場合は以下のように`Build Phases / Link Binary With Libraries`に必要な分のみ参照されていると思います。

<img width="1127" alt="ss 2018-08-24 at 0.10.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/60ff43fb-538c-377c-b539-d0651022c48d.png">


#### ここまでのディレクトリ構成

<img width="270" alt="ss 2018-08-24 at 0.09.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/947c0b5c-3d36-b959-5a75-1d150b9e15dc.png">

```
<foo>.xcodeproj/
Source/
　├ <foo>/
　│　└ <foo>.swift
　└ Info.plist
Tests/
　├ <foo>Tests/
　│　└ <foo>Tests.swift
　└ Info.plist
Carthage/
.build/
.gitignore
Cartfile
Cartfile.private
Cartfile.resolved
LICENSE
Package.swift
README.md
```

#### 参考

- [【Swift】Carthage導入手順](https://qiita.com/ShinokiRyosei/items/9b856ebdec5379b6c631)

## サンプルアプリの作成

iOSサンプルを作成します。`File / New / Project...`から`ExampleApp`という名のiOSプロジェクトを作成します。作成する場所はライブラリ直下 (.xcodeprojのある場所) です。

<img width="717" alt="ss 2018-08-24 at 0.15.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/668f32ef-7a96-a682-c0b8-2a79902ece16.png">

<img width="1512" alt="ss 2018-08-24 at 0.16.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/513c120c-b033-84e1-2d58-729495e7df71.png">

#### ここまでのディレクトリ構成

```
ExampleApp/
　├ ExampleApp.xcodeproj/
　└ ExampleApp/
<foo>.xcodeproj/
Source/
　├ <foo>/
　│　└ <foo>.swift
　└ Info.plist
Tests/
　├ <foo>Tests/
　│　└ <foo>Tests.swift
　└ Info.plist
Carthage/
.build/
.gitignore
Cartfile
Cartfile.private
Cartfile.resolved
LICENSE
Package.swift
README.md
```

## CocoaPodsの準備

```
$ pod spec create <foo>
```

設定等は[自前のライブラリをCocoaPodsで管理するメモ](https://qiita.com/somtd/items/9386e2185adfff4c54bc)などを参考に，登録方法は左記ではなく[Swiftでライブラリを公開する](https://qiita.com/morizotter/items/2e45f8f94c1010ebd69f#_reference-a49d6180f7efda206a63)を参考に。
サンプルアプリはCocoaPods登録後は通常のアプリ開発と同様です。

#### ここまでのディレクトリ構成

```
ExampleApp/
　├ ExampleApp.xcodeproj/
　├ ExampleApp.xcworkspace/
　├ ExampleApp/
　└ Podfile
<foo>.xcodeproj/
Source/
　├ <foo>/
　│　└ <foo>.swift
　└ Info.plist
Tests/
　├ <foo>Tests/
　│　└ <foo>Tests.swift
　└ Info.plist
Carthage/
.build/
.gitignore
Cartfile
Cartfile.private
Cartfile.resolved
LICENSE
Package.swift
README.md
<foo>.podspec
```

#### 参考

- [自前のライブラリをCocoaPodsで管理するメモ](https://qiita.com/somtd/items/9386e2185adfff4c54bc) (:warning: push方法は古いため注意)
- [Swiftでライブラリを公開する](https://qiita.com/morizotter/items/2e45f8f94c1010ebd69f#_reference-a49d6180f7efda206a63)

## Swiftのバージョンを指定する

今回は4.0以上に設定します。`.swift-version`を指定します。

```
$ echo 4.0 > .swift-version
```

## ドキュメントの作成

Jazzyを利用します。Swiftソース上のドキュメントコメントからドキュメントを生成してくれるすごいやつです。

```
$ [sudo] gem install jazzy
$ touch .jazzy.yml
```

設定は[[iOS][Swift] 「Jazzy」を使ってSwiftのソースファイルからドキュメントを生成する](https://dev.classmethod.jp/smartphone/iphone/generate-documentation-using-jazzy/)等を参考に。設定が完了したら以下のコマンドで生成できます。

```
$ jazzy -o docs
```

#### ここまでのディレクトリ構成

```
ExampleApp/
　├ ExampleApp.xcodeproj/
　├ ExampleApp.xcworkspace/
　├ ExampleApp/
　└ Podfile
<foo>.xcodeproj/
Source/
　├ <foo>/
　│　└ <foo>.swift
　└ Info.plist
Tests/
　├ <foo>Tests/
　│　└ <foo>Tests.swift
　└ Info.plist
docs/
　└ index.html
Carthage/
.build/
.jazzy.yml
.swift-version
.gitignore
Cartfile
Cartfile.private
Cartfile.resolved
LICENSE
Package.swift
README.md
<foo>.podspec
```

#### 参考

- [Realm/jazzy - GitHub](https://github.com/Realm/jazzy)
- [[iOS][Swift] 「Jazzy」を使ってSwiftのソースファイルからドキュメントを生成する](https://dev.classmethod.jp/smartphone/iphone/generate-documentation-using-jazzy/)

## Linterの設定

[realm/SwiftLint - GitHub](https://github.com/realm/SwiftLint)を利用します。
`.swiftlint.yml`を作成し，編集します。
日本語だと[SwiftLintのRules全まとめ](https://qiita.com/kagemiku/items/80e6d905dc0059c342b3)等を参考にしましょう。

```
$ brew install swiftlint
$ touch .swiftlint.yml
```

Xcode側では[realm/SwiftLint - GitHub](https://github.com/realm/SwiftLint)の説明の通りに
TARGETの`Build Phases`左上のボタンより，`New Run Script`を選択して以下を入力して下さい。

```sh
if which swiftlint >/dev/null; then
  swiftlint
else
  echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
```

<img width="1123" alt="ss 2018-08-24 at 10.38.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/a79595d5-8241-1d8f-64ea-41a964617223.png">

#### ここまでのディレクトリ構成

```
ExampleApp/
　├ ExampleApp.xcodeproj/
　├ ExampleApp.xcworkspace/
　├ ExampleApp/
　└ Podfile
<foo>.xcodeproj/
Source/
　├ <foo>/
　│　└ <foo>.swift
　└ Info.plist
Tests/
　├ <foo>Tests/
　│　└ <foo>Tests.swift
　└ Info.plist
docs/
　└ index.html
Carthage/
.build/
.jazzy.yml
.swift-version
.swiftlint.yml
.gitignore
Cartfile
Cartfile.private
Cartfile.resolved
LICENSE
Package.swift
README.md
<foo>.podspec
```

#### 参考

- [SwiftLintのRules全まとめ](https://qiita.com/kagemiku/items/80e6d905dc0059c342b3)

## CIの導入

[Travis CI](https://travis-ci.org)を利用します。OSSの範囲であれば無料で使えます。
GitHub連携後，`.travis.yml`を作成します。設定は[Building an Objective-C or Swift Project](https://docs.travis-ci.com/user/languages/objective-c/)を参考に。

```
$ touch .travis.yml
```

RxBluetoothKitではscriptにテストを書いているので参考に作成します。

```
$ mkdir scripts
$ touch ./scripts/all-tests.sh
```

#### ここまでのディレクトリ構成

```
ExampleApp/
　├ ExampleApp.xcodeproj/
　├ ExampleApp.xcworkspace/
　├ ExampleApp/
　└ Podfile
<foo>.xcodeproj/
Source/
　├ <foo>/
　│　└ <foo>.swift
　└ Info.plist
Tests/
　├ <foo>Tests/
　│　└ <foo>Tests.swift
　└ Info.plist
docs/
　└ index.html
Carthage/
.build/
.jazzy.yml
.swift-version
.swiftlint.yml
.gitignore
.travis.yml
Cartfile
Cartfile.private
Cartfile.resolved
LICENSE
Package.swift
README.md
<foo>.podspec
```

#### 参考

- [Travis CI](https://travis-ci.org)
- [GitHubと連携できる継続的インテグレーションツール「Travis CI」入門](https://knowledge.sakura.ad.jp/3754/)
- [Travis CI を使って GitHub 上の Swift プロジェクトをテストする](https://ez-net.jp/article/96/6hS5SChD/S4sD0jPmTLoJ/)

## まとめ

ここでようやく終了です!お疲れさまでした!
でもここは終わりではなく始まりに過ぎません。
それでは皆様，良いOSSライフをお過ごし下さい!