34
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-06-19

はじめに

先日,自作のSwift用のN次元行列演算ライブラリMatftを公開しました(記事github).それにあたって,Matftのような自作ライブラリをXcodeで作成し,公開するまでの一連の流れをまとめたいと思います.

1. 作業用ディレクトリ

1.1 作業用ディレクトリの作成

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

image.png

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

image.png

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

image.png

1.2 構成とその役割

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

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なディレクトリ(≒ソースディレクトリ)を指定します.

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のアカウントとバージョンを指定します.

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用のテストディレクトリを指定します.

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ディレクトリ)にコードを書いていくのですが,アクセス修飾子に注意する必要があります.

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関数にもアクセスできません.ユーザーがどちらも使えるようにするには,openpublicをつけるようにしないといけません.

ライブラリ内
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文などでデバッグ・テストしている方は絶対にこの方法に変えた方が良いと断言します!

以下のような実装を例(ソースディレクトリ)として,テストの仕方をまとめていきます.

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.swiftXCTestManifests.swiftはiOS以外でテストを実行するためのファイルらしいので,削除します.そして,AnimalTests.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で等しいかどうかをテストしていますが,他にもいろいろあり,よく使うものを次項にまとめています.詳しくは公式にまとめられているので,そちらを参照された方が良いと思います.恐らく名前でだいたい何をテストするかはわかると思います.

テストの実行

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

テスト成功例

成功すると,緑のチェックがつきます.

image.png

テスト失敗例

問答無用で名前を"pochi"にするバグがあったとすると,

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()
    }
}

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

image.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を返す関数が実装されていないといけません.

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である必要があります.
例:

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***メソッドを実行する前に呼び出される関数.
overrideして,事前に読み込んだデータなどをtest***メソッドに渡す場合などに使うと良さそう.
tearDown() test***メソッドを実行した後に呼び出される関数.
具体的な使用例は使ったことがないので分かりません.
  • self.measure{}の使用例
AnimalTests
func testInitializerPerformance(){
        self.measure {
            for _ in 0..<10000{
                let kuro = Dog("kuro")
            }
        }
    }
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では,有名どころのライセンスを記述したライセンスファイルを自動で作成することができます.独自のライセンス形態を組む必要がない方は以下の手順で,ライセンスファイルを作成できます.

image.png

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

image.png

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

image.png

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入れて,適宜選びます

Build Setting
select

  • アップデート

update

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にします.

scheme

  • (ライブラリ直下で)ビルドします
cd {your library path}
carthage build --no-skip-current

インストール方法(ユーザー側)

これだけです.簡単ですね!

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とは別に随時更新する必要があります.

{ライブラリ名}.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ファイルの文法を確認します.
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に記述します.
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 両方に対応させる

34
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?