書いて覚えるSwift入門

第45回swift package manager

swift package init

モジュールと言えばswift package manager(略称SPM)で、本連載でも第38回をはじめあちこちで紹介しているのですが、今回は実践編ということで実際に筆者がGitHubで公開しているモジュールを通して解説を進めていくことにします。

パッケージをサポートしたたいていの言語では、最初のパッケージのひな形を一発作成してくれるコマンドが存在します。Swiftのそれはswift package initMyModuleというパッケージを作成するとしたら、

$ mkdir MyModule
$ cd MyModule
$ swift package init

とコマンドを3つ打つと、MyModuleディレクトリ以下に、

Creating library package: MyModule
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/MyModule/MyModule.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/MyModuleTests/
Creating Tests/MyModuleTests/MyModuleTests.swift
Creating Tests/MyModuleTests/XCTestManifests.swift

と最低限必要なファイルが生成されるわけですが、トップディレクトリの作成自体は行わないのがちょっと不思議ではあります。筆者ならswift package init MyModuleとディレクトリ名が指定されている場合は1コマンドで済むようにして、指定がなければカレントディレクトリをトップにするという仕様にしたのですが。

それはさておき、この状態ですでにパッケージとしてはビルドしてテストに成功する状態になっています図1⁠。

図1 モジュールのテストを実施
$ swift test
% swift test
Compile Swift Module 'MyModule' (1 sources)
Compile Swift Module 'MyModuleTests' (2 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/MyModulePackageTests.xctest/Contents/MacOS/MyModulePackageTests
Test Suite 'All tests' started at 2018-12-17 11:01:49.690
Test Suite 'MyModulePackageTests.xctest' started at 2018-12-17 11:01:49.690
Test Suite 'MyModuleTests' started at 2018-12-17 11:01:49.690
Test Case '-[MyModuleTests.MyModuleTests testExample]' started.
Test Case '-[MyModuleTests.MyModuleTests testExample]' passed (0.231 seconds).
Test Suite 'MyModuleTests' passed at 2018-12-17 11:01:49.921.
   Executed 1 test, with 0 failures (0 unexpected) in 0.231 (0.231) seconds
Test Suite 'MyModulePackageTests.xctest' passed at 2018-12-17 11:01:49.921.
   Executed 1 test, with 0 failures (0 unexpected) in 0.231 (0.231) seconds
Test Suite 'All tests' passed at 2018-12-17 11:01:49.921.
   Executed 1 test, with 0 failures (0 unexpected) in 0.231 (0.231) seconds

あとはここから.swiftなファイルを編集したり、Sources/MyExampleディレクトリ以下にSwiftソースファイルを追加したり、Tests/MyModuleTests以下にテストコードを追加したりしていくわけですが、最初にやっておくべきなのが.gitignoreの差し替え。SPMが生成してくれるものは、

.DS_Store
/.build
/Packages
/*.xcodeproj

しかないのですが、実際にXcodeも併用する場合はプロジェクト自体はリポジトリに含めつつプロジェクト内の不要なファイルだけを外すようにしたいですし、SPMだけでなくCocoaPodsCarthageなどのSPMの「先輩」たちとも共存させたい。幸いGitHub自身がメンテナンスしているgithub/gitignoreにSwiftが含まれているので、ここにあるSwift.gitignoreと差し替えるのがよいでしょう。あるいははじめからGitHubでの公開を前提にするのであれば、あらかじめGitHubで新規リポジトリ作成でSwiftを指定してから、それをgit cloneしたあと、cd MyModule && swift package initとしてもよいでしょう。

そこまで終わったらトップディレクトリからgit add .してgit commit -aしておきましょう。

モジュールはなるべくモジュラーに

次に行うべきは、Package.swiftの編集。ほかのパッケージを使わない場合はそのままでもたいていOKですが、そうでない場合はここのdependencies: []の中に.packageを追加しておきます。拙Gitリポジトリでは、swift-floatingpointmathというモジュールを利用するので、

.package(
        url: "https://github.com/dankogai/swift-floatingpointmath.git", from: "0.0.8"
      )

が追加されています。これはexpsinといった、Foundationで追加される数学関数をFloatingPointMathというプロトコルに準拠する型Tの型関数、つまりT.expT.sinとして明示的に指定して使えるようにする、つまり名前空間をプロトコルとして提供するためだけのモジュールですが、これを別モジュールとして分離したことで、swift-bignumなどとのコードの重複を防いでいます。

また名前空間をプロトコルとして提供することで、Double.sqrtComplex.sqrtと別物として利用できてコードの見通しが向上します。

% scripts/run-repl.sh
Welcome to Apple Swift version 4.2.1
(swiftlang-1000.11.42 clang-1000.11.45.1). Type
:help for assistance.
  1> import Complex
  2> Double.sqrt(-1)
$R0: Double = NaN
  3> Complex.sqrt(-1)
$R1: Complex.Complex<Double> = {
  real = 0
  imag = 1
}

余談かつ私見ですが、Foundationはあまりに多くの関数をトップレベルにimportしてくれるおかげで小さなプログラムを手軽に書ける反面、見通しはよろしくない。logは対数なのかログなのか……JavaScriptでもMath.logconsole.logと一目瞭然なのに。もっともFoundationはObjective-Cのレガシィを背負ったモジュールである以上、よりモジュラーなFoundation相当は別途用意すべきなのかもしれませんが。

リリース版をビルド

SPMの各コマンドは、デフォルトではテスト版をビルドします。リリース版をビルドするには、-creleaseを付けます。さらに-Xswiftc -enabletestingを付けると、リリース版でテストもできます。swift-bignumの開発では、これが実際に必要になりました。任意精度有理数型BigRatと任意精度浮動小数点数型BigFloatを提供するモジュールですが、エッジケースが多いのでテストケースが1万5,000を超え、その結果普通にswift testすると筆者の環境で200秒以上かかります図2⁠。

図2 swift testの実行に200秒かかる
% swift test
(……省略……)
Test Suite 'RationalTests' passed at 2018-12-17 12:17:46.703.
   Executed 10 tests, with 0 failures (0 unexpected) in 0.055 (0.055) seconds
Test Suite 'BigNumPackageTests.xctest' passed at 2018-12-17 12:17:46.703.
   Executed 22 tests, with 0 failures (0 unexpected) in 231.611 (231.612) seconds
Test Suite 'All tests' passed at 2018-12-17 12:17:46.703.
   Executed 22 tests, with 0 failures (0 unexpected) in 231.611 (231.612) seconds

Travis CIではもっとかかるので、-c release-Xswiftc -enable-testingを指定するようにしたところ10秒で完了するようになりました図3⁠。

図3 Travis CIでテストを実行した場合
% swift test -c release -Xswiftc -enable-testing
(……省略……)
Test Suite 'RationalTests' passed at 2018-12-17 12:11:59.578.
   Executed 10 tests, with 0 failures (0 unexpected) in 0.009 (0.009) seconds
Test Suite 'BigNumPackageTests.xctest' passed at 2018-12-17 12:11:59.578.
   Executed 22 tests, with 0 failures (0 unexpected) in 10.176 (10.178) seconds
Test Suite 'All tests' passed at 2018-12-17 12:11:59.578.
   Executed 22 tests, with 0 failures (0 unexpected) in 10.176 (10.178) seconds

未サポートのiOSでもSPMしたい

SPMはCPANやRuby Gemsといったほかの言語のパッケージマネージャーにおける開発に慣れた人には非常にとっつきやすいツールですが、Swiftという言語の性格上、1つ重大な問題があります。現時点ではiOSにまったく対応していないのです。前述のswift-complexはもともとほかのモジュールに依存しなかったのですが、swift-floatingpointmathを分離した途端に「オレのiOSどうしてくれるんだ!」という文句が押し寄せました。筆者がとった苦肉の策は、依存ファイルを全部まとめたファイルを自動生成するというもので、scripts/makemono.plというPerlスクリプトでそれを実現していますリスト1⁠。

リスト1 scripts/makemono.pl
#!/usr/bin/env perl
use strict;
use warnings;
use feature ':all';
use FileHandle;
use Fatal;

my $dep = 'FloatingPointMath';
my $target = 'monoComplex.swift';

sub find {
    my $name = shift;
    use File::Find ();
    my @dirs = ('.build/checkouts');
    my @paths;
    File::Find::find(
        {
            wanted => sub {
                $_ eq $name and push @paths, $File::Find::name;
            }
        },
        @dirs
    );
    die "$name not found" if !@paths;
    return @paths;
}
system qw/swift build/;
my $wfh = FileHandle->new($target, 'w');
for my $fn ( <Sources/Complex/*.swift>, find( $dep . '.swift' ) ) {
    my $rfh = FileHandle->new($fn, 'r');
    for (<$rfh>) {
        next if /\Aimport $dep/;
        $wfh->print($_);
    }
}

swift-complex程度の小さなモジュールであればまだいいのですが、これがswift-bignumのような大きくかつ最適化を施さないと実用的な速度が出ないものだと、この手法には無理があります。SPM自体がiOSをサポートしてくれることを首を長くして待っています。

モバイルコンピューティングにモバイルプログラミングを

iOSにネイティブ開発環境がない辛さ。今まで「あればうれしい」だったのが「ないと辛い」になったと一段と強く実感するようになったのが、位置ゲーの隆盛。Ingressで芽吹き、Pokémon GOで花開いた位置ゲーは街角の様相を一変させました。何もなさそうなところに人だかりができても「ああポケモンのジムがあるよね」とお巡りさんも納得する現在、フィールドテストでフィールドデバッグができないもどかしさはどれほどのものか。

その位置ゲーも、テクテクテクテクでNiantic社の寡占状態がいよいよ崩れようとしています図4、図5⁠。ポケストップやジムといった点から点への移動が街区という面を塗りつぶすという同ゲームのメインテーマは画期的で、移動軌跡を後で塗る「予約塗り」と、塗り終わった街区に隣接していればTTPというコストを支払うことでそこにいなくても塗れる「となり塗り」という機能を用意したのは実に見事です。後発だけあってPokémon GOの痒いところがかなり解消されているのですが、それでも実際にフィールドテストすればすぐわかる問題が残っていたのが実に印象的でした。

図4 注目の位置ゲー「テクテクテクテク」マップ表示
図4 注目の位置ゲー「テクテクテクテク」マップ表示
図5 注目の位置ゲー「テクテクテクテク」キャラのステータス表示
図5 注目の位置ゲー「テクテクテクテク」キャラのステータス表示

「街区が広過ぎて、お邪魔モンスターが見つけられない⁠⁠、⁠街区が小さ過ぎて、99%から進まない⁠⁠。これは実際に行けばたちどころにわかるけど室内ではなかなか実感できないことの典型で、現状ではその場でできるのは報告だけで、ちょっとした手直しも難しい。すべてのロジックをサーバ側に持たせてSSHでログインして修正を加えるのは理論的にはできますが現実離れも甚だしい。アプリ全体のリビルドはさておき、その場でパラメータを調整するぐらいのことはできてほしい。iOSアプリをクロスコンパイルしていたのはMacよりも非力だからという技術的な課題はとうの昔に解決しています。位置ゲーに限らず「現場で直したい」課題は増えることはあっても減ることはありません。船上で直せるものは直せるようにしませんか、クック船長?

次回予告

話を戻して、swift-floatingmathではDoubleFloatを直接extendするのではなく、Float ingPointMathというプロトコルを用意したうえでそれらの型を準拠させるようにしています。なぜそうしたのか?――次回はモジュールとプロトコルの関係について解説します。

Software Design

本誌最新号をチェック!
Software Design 2022年9月号

2022年8月18日発売
B5判/192ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    MySQL アプリ開発者の必修5科目
    不意なトラブルに困らないためのRDB基礎知識
  • 第2特集
    「知りたい」⁠使いたい」⁠発信したい」をかなえる
    OSSソースコードリーディングのススメ
  • 特別企画
    企業のシステムを支えるOSとエコシステムの全貌
    [特別企画]Red Hat Enterprise Linux 9最新ガイド
  • 短期連載
    今さら聞けないSSH
    [前編]リモートログインとコマンドの実行
  • 短期連載
    MySQLで学ぶ文字コード
    [最終回]文字コードのハマりどころTips集
  • 短期連載
    新生「Ansible」徹底解説
    [4]Playbookの実行環境(基礎編)

おすすめ記事

記事・ニュース一覧