SwiftPM で作る iOS 向け Static library を作ってみた

今回はサンプルとして下記のようなインスタンス生成時にプロパティを変更するためのユーティリティツールを追加するライブラリーを作成してみます

public protocol Preparable: AnyObject {}
public extension Preparable {
  @discardableResult func then(_ processingBlock: (Self) -> Void) -> Self {
    processingBlock(self) return self
  }
}
extension NSObject: Preparable {}
  UIView() .then { $0.backgroundColor = .white
}

完成したプロジェクトはこちら

公式のドキュメントからパッケージの初期化の試す

apple/swift-package-manager

$ mkdir Preparable
$ cd Preparable
$ swift package init # or swift package init --type library
$ swift build
$ swift test

ディレクトリ名(上記のコードだと Preparable)がパッケージ名になるっぽいです。--type で作成するモジュールのテンプレートを変更できるとのことですが、今回はライブラリなので--type を指定しない状態で初期化します swift package init で生成されたプロジェクトの構成をみると、

  • Package.swift
  • Sources
  • Tests

の三つが生成されました。実際に Sources にライブラリのコードを追加していきます。

実装していく

自作ライブラリを作る上で外部のライブラリ(例えば RxSwift など)に依存する必要が出てくるかも知れません。その場合は Package.swift に依存関係を記述する必要があります。 RxSwift を例にあげると


import PackageDescription


let package = Package(
    name: "Preparable",
    products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .library(
            name: "Preparable",
            targets: ["Preparable"]),
    ],
    dependencies: [
        ///依存するパッケージを追加する。プロジェクトに対する依存なのでテストで使用するパッケージもここに追加する
        .package(url: https://github.com/ReactiveX/RxSwift.git, from: "5.0.0"),
    ],
    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: "Preparable",
            dependencies: ["RxSwift"]),
        .testTarget(
            name: "PreparableTests",
            dependencies: ["Preparable"]),
    ]
)

このように依存関係を記述するとプロジェクトで外部ライブラリを使用した開発ができます。 今回は軽めの実装なのでこれからテストを書いていきますが もし大きめのプロジェクトを作るのであれば先にテストを用意して、都度 swift test でテストすると良いと思います。

テストコードは以下のようなものです。呼び出された関数で変更されたプロパティが返り値で返却されたインスタンスで本当に変更されているのかチェックしています


@testable import Preparable
import XCTest


final class PreparableTests: XCTestCase {
    func testPreparableObject() {
        let mock = Mock(count: 0)
        XCTAssertEqual(mock.count, 0)
        mock
            .then {
                $0.count = 100
            }
        XCTAssertEqual(mock.count, 100)
    }


    static var allTests = [
        ("testPreparableObject", testPreparableObject)
    ]
}

基本的には Coverage100%を目指します XCode での Coverage 計測はこちら

code linting と build、test を一括で実行するスクリプトを用意しておくと便利です。

作成したライブラリを実際に使ってみる

実際に完成したライブラリをプロジェクトから呼び出してみます

開発しているプロジェクトをひらいた状態で Xcode のステータスバーの File > Swift Package > Add Package Dependencies...から Github 上にあるライブラリのリンクを追加してインポートします。 インポートに成功するとプロジェクトで使用することが確認できました。

まとめ

CocoaPods や Carthage に比べて SwiftPM はまだできることに制限がありますがそれでも十分に実務でも使用できるレベルだと思います。SwiftUI と合わせて積極的に活用してみたいと思います。

プロフィール

てらにゃん

19歳学生。よく書くプログラミング言語はSwift, Typescript。業務委託のお仕事はお断りさせていただいています。インターン、アルバイトのお誘いは大歓迎です!

Contact me

Twitter: @tera_ny
Github: @g4zeru