Swiftで自作ライブラリを公開する手順

# はじめに
先日，自作のSwift用のN次元行列演算ライブラリMatftを公開しました([記事](https://qiita.com/jjjkkkjjj/items/1f2b5c3835b1600d3129)，[github](https://github.com/jjjkkkjjj/Matft))．それにあたって，Matftのような自作ライブラリをXcodeで作成し，公開するまでの一連の流れをまとめたいと思います．

# 1. 作業用ディレクトリ
## 1.1 作業用ディレクトリの作成
Xcodeを開き，File > New > Swift Packageでライブラリ用のディレクトリを作成します．既存のディレクトリをライブラリとして公開するには，後述の`Package.swift`ファイルを自分で作成すれば良いです．

<img width="549" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253935/b8f61744-6ca1-a9ac-cded-b61f69424534.png">

今回は例として，`Animal`という名のライブラリを作成します．

<img width="839" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253935/0a264b93-2fd1-9438-925e-6def3dd52b90.png">


すると以下のような画面になり，`Animal`ディレクトリが作成されると思います．

<img width="775" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253935/3bdf2aa5-13db-b34c-86d7-d3e301a9972a.png">


## 1.2 構成とその役割

↑で`Animal`ディレクトリが作成されると，その直下のディレクトリの構成はこのようになります．

```bash:test
├── Package.swift
├── README.md
├── Sources
│   └── Animal
│       └── Animal.swift
└── Tests
    ├── LinuxMain.swift
    └── AnimalTests
        ├── XCTestManifests.swift
        └── AnimalTests.swift
```

### Package.swift

公式のライブラリ管理ツール`Swift Package Manager`に対応させるためのファイルです．
ここに，ライブラリの基本情報，依存関係，ソース・テストディレクトリの指定等の情報を記述します．

- ライブラリの基本情報

`.library`にある`name`で名前，`targets`でexecutableなディレクトリ（≒ソースディレクトリ）を指定します．

```swift:Package.swift
products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .library(
            name: "Animal",
            targets: ["Animal"]),
    ]
```

- 依存関係

`.package`をコメントアウトして，他パッケージのgithubのアカウントとバージョンを指定します．

```swift:Package.swift
dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
```

- ソースディレクトリ・テストディレクトリの指定

`targets`の`.target`でbuild用のソースディレクトリを，`.testTarget`でbuild用のテストディレクトリを指定します．

```swift:Package.swift
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 which this package depends on.
        .target(
            name: "Animal",
            dependencies: []),
        .testTarget(
            name: "AnimalTests",
            dependencies: ["Animal"]),
    ]
```

### README.md

ご存知でしょうので割愛します．わかりやすく書きましょう．

### Sources

ソースディレクトリです．ここに，クラスや関数を定義していきます．ソースの書き方は後述します．

### Tests

テストディレクトリです．ライブラリを公開する場合は，**テストは必須**だと思います．面倒なので，省略したいところですが，バグを防ぐためにも必ずテストコードを書いてください．テストの書き方も後述します．

# 2. コードを書く

あとは，[Sources](#Sources)で設定したディレクトリ（デフォルトでは`Sources`ディレクトリ）にコードを書いていくのですが，アクセス修飾子に注意する必要があります．

## 2.1 アクセス修飾子

Swiftにはアクセス修飾子が5つあり，以下のようになっています．（[[Swift]アクセス修飾子](https://qiita.com/GATA/items/90341f56a56ff584e756)より）

| アクセス修飾子  | 説明 　 　　 |
|:--|:--|
| open  | 別モジュールから呼び出せる。継承やオーバーライドが可能 |
| public  | 別モジュールから呼び出せるが継承やオーバーライドが不可能 |
| internal  | 同モジュール内からのみ呼び出せる |
| fileprivate  | 同ファイル内からのみ呼び出せる |
| private  | 同スコープ内からのみ呼び出せる |

ここで注意したいのが，この**アクセス修飾子を省略した場合は`internal`としてクラスや関数が定義される**ことです．つまり，クラスや関数の定義でアクセス修飾子を普段のクセで省略してしまうと，ライブラリのユーザーは，その省略されたクラスや関数を使うことができなくなります．

例えば，`Animalライブラリ`が`Dog`クラスと2匹のDogの名前をつなげてprintする関数の実装が以下のようになされているとします．ご覧のようにアクセス修飾子は特に明示されていません．

```swift:ライブラリ内
class Dog{
    var name: String

}

func get_2dogs_name(_ a: Dog, _ b: Dog){
    print("\(a.name) and \(b.name)")
}
```

```swift:ユーザー
let pochi = Dog(name: "pochi") //これも
let inu = Dog(name: "inu") //これも
get_2dogs_name(pochi, inu)　//これもコンパイラに怒られる
```

この場合は，`Animalライブラリ`のユーザーは`Dog`クラスにも`get_2dogs_name`関数にもアクセスできません．ユーザーがどちらも使えるようにするには，`open`や`public`をつけるようにしないといけません．

```swift:ライブラリ内
open class Dog{
    public var name: String
}

public func get_2dogs_name(_ a: Dog, _ b: Dog){
    print("\(a.name) and \(b.name)")
}
```

なので，ユーザーが悲しい思いをしないようにするためにも，例え`internal`だったとしても，普段からアクセス修飾子は明示的につけるように癖づけておいた方が良さそうですね．

## 2.2 テスト
### テストの行い方
ある程度ソースコードを書いたら，適宜テストをしましょう．ありがたいことに，Xcodeにはテスト機能がしっかりと整備されています．これを使わない手はないので使いましょう．
＃Swiftやライブラリの作成に限ったことではないですが，テストは本当に大事だと思います．
＃もし，print文などでデバッグ・テストしている方は絶対にこの方法に変えた方が良いと断言します！

以下のような実装を例（ソースディレクトリ）として，テストの仕方をまとめていきます．

```swift:Sources/Animal/Animal.swift
open class Dog{
    public var name: String
    internal var _id: Int
    public init(_ name: String){
        self.name = name
        self._id = IDManager.shared.grant_id()
    }
}

internal class IDManager{
    static let shared = IDManager()
    private var _id_num = -1
    
    internal func grant_id() -> Int{
        self._id_num += 1
        return self._id_num
    }
}

```

まず，`LinuxMain.swift`と`XCTestManifests.swift`はiOS以外でテストを実行するためのファイルらしいので，削除します．そして，`AnimalTests.swift`にテストしたい内容を記述していきます．何をしているかはコメントを参照して下さい．

```swift:Tests/AnimalTests/AnimalTests.swift
import XCTest //テスト用のモジュールをインポート
@testable import Animal //ソースをインポート．@testableをつけることによって，internal修飾子にもアクセスできるようになります．

final class AnimalTests: XCTestCase {//XCTestCaseを継承したクラスを作成

    func testInitializer() { //Prefixに"test"をつけた関数を実行します．
        do{
            let pochi = Dog("pochi")
            XCTAssertEqual(pochi.name, "pochi") //pochi.nameが"pochi"と等しいか確認
            XCTAssertEqual(pochi._id, 0) //pochi._idが0と等しいか確認
        }
        do{
            let shiro = Dog("shiro")
            XCTAssertEqual(shiro.name, "shiro")
            XCTAssertEqual(shiro._id, 1)
        }
    }
}

```

大事な点は，3点あります．

#### `@testable`付きimport

`@testable import Animal`で`@testable`をつけると，このファイル内では上述のinternal修飾子のクラスや関数にもアクセスできるようになります．
→`Dog`クラスの`internal var _id`プロパティにアクセスできていることが分かりますね．


#### `test***`メソッド

`XCTestCase`を継承したクラスで`test***`メソッドをテスト関数として実行します．
→`***`に入るのはなんでもよく，テストしたいものに合わせて名前をつけると良いと思います．

#### `XCTest`の関数でテスト

`XCTest`の関数（例：`XCTAssertEqual`）を使って，テストします．
→例では，`XCTAssertEqual`で等しいかどうかをテストしていますが，他にもいろいろあり，よく使うものを[次項](#テスト関数一覧)にまとめています．詳しくは[公式](https://developer.apple.com/documentation/xctest)にまとめられているので，そちらを参照された方が良いと思います．恐らく名前でだいたい何をテストするかはわかると思います．

#### テストの実行

テストコードが完成したら，`Cmd+U`でテストを実行できます．

#### テスト成功例
成功すると，緑のチェックがつきます．

<img width="1181" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253935/3d1b8920-16c6-e92d-b717-02f19abdae20.png">



#### テスト失敗例
問答無用で名前を`"pochi"`にするバグがあったとすると，

```swift:Animal.swift
open class Dog{
    public var name: String
    internal var _id: Int
    public init(_ name: String){
        self.name = "pochi"
        self._id = IDManager.shared.grant_id()
    }
}
```

以下のように赤のバツで示されます．

<img width="1181" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253935/11d5d9e3-8c64-f3b9-8f53-fc6b0db7fd67.png">



### テスト関数一覧

#### Boolean判定系

| 関数名 | テストする内容  |
|---|---|
| XCTAssert |  与えられたexpression引数がtrueを返すとOK． |
| XCTAssertTrue | 未確認ですが↑と同じだと思います.|
| XCTAssertFalse  | 与えられたexpression引数がfalseを返すとOK． |

#### Nil判定系

| 関数名 | テストする内容  |
|---|---|
| XCTAssertNil |  与えられたexpression引数がnilを返すとOK． |
| XCTAssertNotNil | 与えられたexpression引数がnilでないとOK．|
| XCTUnwrap  | 与えられたexpression引数がnilでないかつ値をもつとOK． |


#### 同値判定系※

| 関数名 | テストする内容  |
|---|---|
| XCTAssertEqual |  与えられた2つの引数（expression1とexpression2）のexpression1がexpression2と同じであるとOK． |
| XCTAssertNotEqual | 与えられた2つの引数（expression1とexpression2）のexpression1がexpression2と違うとOK．|

※自前のクラスに対して，同値判定を行う場合は，そのクラスが`Equatable`である必要があります．つまり，例の`Dog`クラスを用いるのであれば，以下のようにBoolを返す関数が実装されていないといけません．

```swift:Animal.swift
extension Dog: Equatable{
    public static func == (lhs: Dog, rhs: Dog) -> Bool {
        return lhs.name == rhs.name
    }
}
```


#### 比較判定系※

| 関数名 | テストする内容  |
|---|---|
| XCTAssertGreaterThan |  与えられた2つの引数（expression1とexpression2）のexpression1がexpression2より大きければOK． |
| XCTAssertGreaterOrThan | 与えられた2つの引数（expression1とexpression2）のexpression1がexpression2以上であればOK．|
| XCTAssertLessThan  | 与えられた2つの引数（expression1とexpression2）のexpression1がexpression2より小さければOK． |
| XCTAssertLessOrThan |  与えられた2つの引数（expression1とexpression2）のexpression1がexpression2以下であればOK． |

※同値判定同様，自前のクラスである場合は，`Comparable`である必要があります．
例：

```swift:Animal.swift
extension Dog: Comparable{
    public static func < (lhs: Dog, rhs: Dog) -> Bool {
        return lhs._id < rhs._id
    }
}
```

#### エラーハンドリング系

| 関数名 | テストする内容  |
|---|---|
| XCTAssertThrowsError |  与えられたexpression引数がエラーを吐けばOK． |
| XCTAssertNoThrow | 与えられたexpression引数がエラーを吐かなければOK．|


#### `XCTestCase`のメソッド

| 関数名 | 内容  |
|---|---|
| measure{} |  クロージャになっていて，この中に書かれた処理時間を計算してくれます．（下の例参照）|
| setUp() | `test***`メソッドを実行する前に呼び出される関数．<br>overrideして，事前に読み込んだデータなどを`test***`メソッドに渡す場合などに使うと良さそう．|
| tearDown() | `test***`メソッドを実行した後に呼び出される関数．<br>具体的な使用例は使ったことがないので分かりません．|

- `self.measure{}`の使用例

```swift:AnimalTests
func testInitializerPerformance(){
        self.measure {
            for _ in 0..<10000{
                let kuro = Dog("kuro")
            }
        }
    }
```

```console:consoleの出力
Animal/Tests/AnimalTests/AnimalTests.swift:20: Test Case '-[AnimalTests.AnimalTests testInitializerPerformance]' measured [Time, seconds] average: 0.008,
relative standard deviation: 13.806%,
values: [0.010634, 0.007030, 0.006645, 0.007511, 0.007080, 0.007037, 0.007481, 0.008026, 0.007512, 0.007762], 
performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", 
baselineAverage: , maxPercentRegression: 10.000%,  maxPercentRelativeStandardDeviation: 10.000%, 
maxRegression: 0.100, maxStandardDeviation: 0.100
```

※デフォルトでは，debugモードでの実行時間を計測するので，より厳密な実行時間を計測するにはreleaseモードで行った方が良いと思います．

# 3. 公開

テストも無事完了したら，いよいよ公開です．Swiftでは，ライブラリの管理ツールがいくつかあり，ユーザーに使ってもらうためにも代表的なものには対応させたいところです．代表的なライブラリ管理ツールは以下の3つで，

- Swift Package Manager
- Carthage
- CocoaPods

今回はユーザーがこれらのツール経由でインストールできるように自作のライブラリを対応させたいと思います．
※基本的にはGithubでプログラムを管理しているものとして話を進めていきます．

## 3.1 ライセンスファイルの作成

管理ツールに対応させる前に，ライセンスを決めます．Githubでは，有名どころのライセンスを記述したライセンスファイルを自動で作成することができます．独自のライセンス形態を組む必要がない方は以下の手順で，ライセンスファイルを作成できます．

<img width="1008" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253935/c5d7175d-4e92-5a22-9e83-7807a5b08736.png">

”LICENSE”と打ち込むと右側に”Choose a lisence template”というボタンが出てくるので，それを押します．

<img width="1030" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253935/6925695c-a315-58ef-06a2-639e722f2aaa.png">

有名どころのテンプレートが出てくるので，選べば自動で作成してくれます．

<img width="1182" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253935/c3695b21-7334-9c70-fb74-e20e53151062.png">



## 3.2 Swift Package Manager

[Package.swift](#Package.swift)ファイルを作成すれば良いです．書き方は[Package.swift](#Package.swift)を参照してください．

### 対応方法（開発者側）

- バージョンができたら，commitにタグをつけ，pushするだけで良いです．

```bash
git tag -a x.x.x -m "メッセージ" {commit id}
# 例
# git tag -a 0.1.2 -m "猫追加" 3dqse1aaa9
git push --tags
```

※**x.x.x**の形でないといけない
※ブランチだとたぶんうまくいかない


- 誤ってタグをつけた場合（タグの削除）

```bash
git tag --delete {tagname}
# 例
# git tag --delete 0.1.2
git push origin :{tagname}
# 例
# git push origin :0.1.2
```

### インストール方法（ユーザー側）

めちゃくちゃ簡単ですね！

- Project > Build Setting > + でgitのURL入れて，適宜選びます

![Build Setting](https://user-images.githubusercontent.com/16914891/77144994-b0c72280-6aca-11ea-8633-1fb1a13ec74d.png)
![select](https://user-images.githubusercontent.com/16914891/77144995-b1f84f80-6aca-11ea-8f4d-911bd96013cb.png)
    
- アップデート

![update](https://user-images.githubusercontent.com/16914891/77145225-4367c180-6acb-11ea-98ea-8d7a5a2a669f.png)

## 3.3 Carthageへの対応

### 対応方法（開発者側）

- Carthageをインストールします．

```bash
brew update
brew install carthage
```
  
- dynamic frameworkの用意（`〜.xcodeproj`の作成）します．

```bash
swift package generate-xcodeproj
```

- 作成された`〜.xcodeproj`を開き，File > New Target > iOS > frameworkでframeworkを作成します．（既にtargetがある場合は省略）

- Product > Scheme > Manage SchemesからSchemeを以下のようにSharedにします．

![scheme](https://user-images.githubusercontent.com/16914891/77147395-7496c080-6ad0-11ea-84d9-5c9ee8cab01a.png)
  
- （ライブラリ直下で）ビルドします

```/bin/bash
cd {your library path}
carthage build --no-skip-current
```

- 問題なければ，[3.1 Swift Package Manager](#3.1-Swift-Package-Manager)同様gitでタグ付けすればOKです．

### インストール方法（ユーザー側）

これだけです．簡単ですね！

```bash
echo 'github "{ライブラリのOwnerのユーザー名}/{repository名}"' > Cartfile
carthage update ###or append '--platform ios'
```

## 3.4 CocoaPodsへの対応

### 対応方法（開発者側）

- cocoapodsをインストールします．

```bash
sudo gem install cocoapods
```

- podspecファイルを作成します．`{ライブラリ名}.podspec`ファイルが作成されます．

```bash
cd {your library path}
pod spec create {ライブラリ名}
```

- 適宜編集します．（コメントを参照してください）

※`spec.version`はgitのtagとは別に随時更新する必要があります．

```bash:{ライブラリ名}.podspec
Pod::Spec.new do |spec|
  spec.name           = "Matft" #ライブラリ名
  spec.version        = "0.1.1" #バージョン番号
  spec.summary        = "Numpy-like matrix operation library in swift" #要約
  spec.homepage       = "https://github.com/jjjkkkjjj/Matft" #ライブラリ名
  spec.license        = { :type => 'BSD-3-Clause', :file => 'LICENSE' } #ライセンス
  spec.author         = "jjjkkkjjj" #作者名
  spec.platform       = :ios, "10.0" #プラットフォーム
  spec.swift_versions = "4.0" #Swiftのバージョン
  spec.pod_target_xcconfig  = { 'SWIFT_VERSION' => '4.0' } #Swiftのバージョン．上との違いがわかっていません．
  spec.source         = { :git => "https://github.com/jjjkkkjjj/Matft.git", :tag => "#{spec.version}" } #ソース情報を指定
  spec.source_files   = "Sources/**/*" #ソースファイルを正規表現で指定
end
```

- `{ライブラリ名}.podspec`ファイルの文法を確認します．

```bash
pod lib lint
```

※warningは無視することができます．

- cocoapodsに開発者情報を登録します．（初回のみ）

```bash
pod trunk register ~@mail 'name'
# 例
# pod trunk register hoge@fuga.com 'hugaga'
```

→メールが届くので，認証してください．

- cocoapodsへライブラリを登録（ライブラリの更新）

```bash
pod trunk push {ライブラリ名}.podspec #--allow-warnings
```

※--allow-warningsはwarningを無視します．

何もなければおめでとう🎉的なメッセージが出力され，cocoapodsへの登録（更新）が完了します．

- 誤って登録（更新）した場合
    
```bash
pod trunk delete {ライブラリ名} x.x.x(version number)
pod cache clean --all
```

### インストール方法（ユーザー側）

- `Podfile`を作成します． (既にある場合は省略)

```bash
pod init
```

- `pod '{ライブラリ名}'`を`Podfile`に記述します．

```bash:Podfile
target 'your project' do
  pod 'Matft'
end
```

- インストールします．

```bash
pod install
```

# おわりに

以上がライブラリの作成〜公開の手順です．お疲れ様でした．


# 参考
[[Swift]アクセス修飾子](https://qiita.com/GATA/items/90341f56a56ff584e756)
[iOS アプリの Unit Test - Swift 編](https://qiita.com/s-harada/items/5a8c12b0c456d155ba53)
[自作ライブラリのSwift Package Manager(SwiftPM)対応](https://qiita.com/am10/items/72dbc511efc512fc065a)
[【iOS】オープンソースSwiftライブラリのつくり方](https://qiita.com/shtnkgm/items/0f62398c66af159401a6)
[iOSアプリ開発にSwift Package Managerを使おう](https://qiita.com/hironytic/items/09a4c16857b409c17d2c)
[自作ライブラリを CocoaPods と Carthage 両方に対応させる](https://qiita.com/mokumoku/items/340e089f34a284c0532a)