はじめに
先日,自作のSwift用のN次元行列演算ライブラリMatftを公開しました(記事,github).それにあたって,Matftのような自作ライブラリをXcodeで作成し,公開するまでの一連の流れをまとめたいと思います.
1. 作業用ディレクトリ
1.1 作業用ディレクトリの作成
Xcodeを開き,File > New > Swift Packageでライブラリ用のディレクトリを作成します.既存のディレクトリをライブラリとして公開するには,後述のPackage.swift
ファイルを自分で作成すれば良いです.
今回は例として,Animal
という名のライブラリを作成します.
すると以下のような画面になり,Animal
ディレクトリが作成されると思います.
1.2 構成とその役割
↑でAnimal
ディレクトリが作成されると,その直下のディレクトリの構成はこのようになります.
├── 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なディレクトリ(≒ソースディレクトリ)を指定します.
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のアカウントとバージョンを指定します.
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
- ソースディレクトリ・テストディレクトリの指定
targets
の.target
でbuild用のソースディレクトリを,.testTarget
でbuild用のテストディレクトリを指定します.
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
ディレクトリ)にコードを書いていくのですが,アクセス修飾子に注意する必要があります.
2.1 アクセス修飾子
Swiftにはアクセス修飾子が5つあり,以下のようになっています.([Swift]アクセス修飾子より)
アクセス修飾子 | 説明 |
---|---|
open | 別モジュールから呼び出せる。継承やオーバーライドが可能 |
public | 別モジュールから呼び出せるが継承やオーバーライドが不可能 |
internal | 同モジュール内からのみ呼び出せる |
fileprivate | 同ファイル内からのみ呼び出せる |
private | 同スコープ内からのみ呼び出せる |
ここで注意したいのが,このアクセス修飾子を省略した場合はinternal
としてクラスや関数が定義されることです.つまり,クラスや関数の定義でアクセス修飾子を普段のクセで省略してしまうと,ライブラリのユーザーは,その省略されたクラスや関数を使うことができなくなります.
例えば,Animalライブラリ
がDog
クラスと2匹のDogの名前をつなげてprintする関数の実装が以下のようになされているとします.ご覧のようにアクセス修飾子は特に明示されていません.
class Dog{
var name: String
}
func get_2dogs_name(_ a: Dog, _ b: Dog){
print("\(a.name) and \(b.name)")
}
let pochi = Dog(name: "pochi") //これも
let inu = Dog(name: "inu") //これも
get_2dogs_name(pochi, inu) //これもコンパイラに怒られる
この場合は,Animalライブラリ
のユーザーはDog
クラスにもget_2dogs_name
関数にもアクセスできません.ユーザーがどちらも使えるようにするには,open
やpublic
をつけるようにしないといけません.
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文などでデバッグ・テストしている方は絶対にこの方法に変えた方が良いと断言します!
以下のような実装を例(ソースディレクトリ)として,テストの仕方をまとめていきます.
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
にテストしたい内容を記述していきます.何をしているかはコメントを参照して下さい.
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
で等しいかどうかをテストしていますが,他にもいろいろあり,よく使うものを次項にまとめています.詳しくは公式にまとめられているので,そちらを参照された方が良いと思います.恐らく名前でだいたい何をテストするかはわかると思います.
テストの実行
テストコードが完成したら,Cmd+U
でテストを実行できます.
テスト成功例
成功すると,緑のチェックがつきます.
テスト失敗例
問答無用で名前を"pochi"
にするバグがあったとすると,
open class Dog{
public var name: String
internal var _id: Int
public init(_ name: String){
self.name = "pochi"
self._id = IDManager.shared.grant_id()
}
}
以下のように赤のバツで示されます.
テスト関数一覧
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を返す関数が実装されていないといけません.
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
である必要があります.
例:
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*** メソッドを実行する前に呼び出される関数.overrideして,事前に読み込んだデータなどを test*** メソッドに渡す場合などに使うと良さそう. |
tearDown() |
test*** メソッドを実行した後に呼び出される関数.具体的な使用例は使ったことがないので分かりません. |
-
self.measure{}
の使用例
func testInitializerPerformance(){
self.measure {
for _ in 0..<10000{
let kuro = Dog("kuro")
}
}
}
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では,有名どころのライセンスを記述したライセンスファイルを自動で作成することができます.独自のライセンス形態を組む必要がない方は以下の手順で,ライセンスファイルを作成できます.
”LICENSE”と打ち込むと右側に”Choose a lisence template”というボタンが出てくるので,それを押します.
有名どころのテンプレートが出てくるので,選べば自動で作成してくれます.
3.2 Swift Package Manager
Package.swiftファイルを作成すれば良いです.書き方はPackage.swiftを参照してください.
対応方法(開発者側)
- バージョンができたら,commitにタグをつけ,pushするだけで良いです.
git tag -a x.x.x -m "メッセージ" {commit id}
# 例
# git tag -a 0.1.2 -m "猫追加" 3dqse1aaa9
git push --tags
※x.x.xの形でないといけない
※ブランチだとたぶんうまくいかない
- 誤ってタグをつけた場合(タグの削除)
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入れて,適宜選びます
- アップデート
3.3 Carthageへの対応
対応方法(開発者側)
- Carthageをインストールします.
brew update
brew install carthage
- dynamic frameworkの用意(
〜.xcodeproj
の作成)します.
swift package generate-xcodeproj
-
作成された
〜.xcodeproj
を開き,File > New Target > iOS > frameworkでframeworkを作成します.(既にtargetがある場合は省略) -
Product > Scheme > Manage SchemesからSchemeを以下のようにSharedにします.
- (ライブラリ直下で)ビルドします
cd {your library path}
carthage build --no-skip-current
- 問題なければ,3.1 Swift Package Manager同様gitでタグ付けすればOKです.
インストール方法(ユーザー側)
これだけです.簡単ですね!
echo 'github "{ライブラリのOwnerのユーザー名}/{repository名}"' > Cartfile
carthage update ###or append '--platform ios'
3.4 CocoaPodsへの対応
対応方法(開発者側)
- cocoapodsをインストールします.
sudo gem install cocoapods
- podspecファイルを作成します.
{ライブラリ名}.podspec
ファイルが作成されます.
cd {your library path}
pod spec create {ライブラリ名}
- 適宜編集します.(コメントを参照してください)
※spec.version
はgitのtagとは別に随時更新する必要があります.
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
ファイルの文法を確認します.
pod lib lint
※warningは無視することができます.
- cocoapodsに開発者情報を登録します.(初回のみ)
pod trunk register ~@mail 'name'
# 例
# pod trunk register hoge@fuga.com 'hugaga'
→メールが届くので,認証してください.
- cocoapodsへライブラリを登録(ライブラリの更新)
pod trunk push {ライブラリ名}.podspec #--allow-warnings
※--allow-warningsはwarningを無視します.
何もなければおめでとう🎉的なメッセージが出力され,cocoapodsへの登録(更新)が完了します.
- 誤って登録(更新)した場合
pod trunk delete {ライブラリ名} x.x.x(version number)
pod cache clean --all
インストール方法(ユーザー側)
-
Podfile
を作成します. (既にある場合は省略)
pod init
-
pod '{ライブラリ名}'
をPodfile
に記述します.
target 'your project' do
pod 'Matft'
end
- インストールします.
pod install
おわりに
以上がライブラリの作成〜公開の手順です.お疲れ様でした.
参考
[Swift]アクセス修飾子
iOS アプリの Unit Test - Swift 編
自作ライブラリのSwift Package Manager(SwiftPM)対応
【iOS】オープンソースSwiftライブラリのつくり方
iOSアプリ開発にSwift Package Managerを使おう
自作ライブラリを CocoaPods と Carthage 両方に対応させる