書いて覚えるSwift入門

第38回Swift Package Manager

Swiftにcpan相当のツールはあるのか?

モダンプログラミング言語は、単にプログラムを

  • 書いて
  • 実行する

だけではなく、

  • 機能をライブラリ(library)にまとめ、
  • モジュール(module)として公開し
  • それを援用する

機能まで備えているものです。Perlのcpanを嚆矢こうしに、Pythonならpip、Rubyならgem、ECMAScriptならnpm……ではSwiftは?

あります! Swift 3以降は。その名もSwiftPackage Manager。それ以前から同様の機能を提供するサードパーティー製のツールとしてはCarthageCocoaPodsが存在していましたが、Swift Package Managerはその名のとおりSwiftに標準装備され、すべての作業がSwiftで完結するという点で画期的です。まだまだ「老舗」ツールに比べると機能面で見劣りはしますが、標準装備というのはそれを上回る利点ですし、MacだけでなくLinuxでも同じように使えることで、とくにServer Side Swiftではこれが標準となっていくものと断言できるところまで来ました。

ライブラリを作る

ではさっそく、書いて覚えていきましょう。ここではFizzBuzzモジュールを作ってみます。Terminal.appから次のコマンドを実行します。

$ mkdir FizzBuzz
$ cd FizzBuzz
$ swift package init # --type=library
 ……(中略)……
Creating library package: FizzBuzz
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/FizzBuzz/FizzBuzz.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/FizzBuzzTests/
Creating Tests/FizzBuzzTests/FizzBuzzTests.swift
Creating Tests/FizzBuzzTests/XCTestManifests.swift

実はこの時点でビルドしてテストできる状態になっています。

$ swift build

で、

Compile Swift Module 'FizzBuzz' (1 sources)
 ……(中略)……
$ swift test

となり、図1のようになります。

図1 ライブラリの作成
Compile Swift Module 'FizzBuzzTests' (2 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/FizzBuzzPackageTests.xctest/Contents/MacOS/
FizzBuzzPackageTests
Test Suite 'All tests' started at 2018-05-21 07:23:10.304
Test Suite 'FizzBuzzPackageTests.xctest' started at 2018-05-21 07:23:10.305
Test Suite 'FizzBuzzTests' started at 2018-05-21 07:23:10.305
Test Case '-[FizzBuzzTests.FizzBuzzTests testExample]' started.
Test Case '-[FizzBuzzTests.FizzBuzzTests testExample]' passed (0.085 seconds).
Test Suite 'FizzBuzzTests' passed at 2018-05-21 07:23:10.390.
   Executed 1 test, with 0 failures (0 unexpected) in 0.085 (0.085) seconds
Test Suite 'FizzBuzzPackageTests.xctest' passed at 2018-05-21 07:23:10.390.
   Executed 1 test, with 0 failures (0 unexpected) in 0.085 (0.085) seconds
Test Suite 'All tests' passed at 2018-05-21 07:23:10.390.
   Executed 1 test, with 0 failures (0 unexpected) in 0.085 (0.085) seconds

しかしこの状態ではとくに何もしていません。実際にFizzBuzzしてみましょう。

$ swift package generate-xcodeproj

とすると Xcode Project が生成されるので、

$ open FizzBuzz.xcodeproj

で開きます図2⁠。

図2 FizzBuzz.xcodeprojの実行
図2 FizzBuzz.xcodeprojの実行

まずはモジュールを書き直しましょう。リスト1のとおり上書きします。Intfizzbuzzプロパティを追加するだけの簡単なモジュールです。

リスト1 Sources/FizzBuzz/FizzBuzz.swift
extension Int {
    public var fizzbuzz:String {
        let fb = ("Fizz", "Buzz")
        switch (self % 3, self % 5) {
        case (0, 0) : return fb.0 + fb.1
        case (0, _) : return fb.0
        case (_, 0) : return fb.1
        default : return "\(self)"
        }
    }
}

テストも書き直しましょうリスト2⁠。

リスト2 Tests/FizzBuzzTests/FizzBuzzTests.swift
final class FizzBuzzTests: XCTestCase {
    func test0() { XCTAssertEqual(0.fizzbuzz, "FizzBuzz") }
    func test1() { XCTAssertEqual(1.fizzbuzz, "1") }
    func test2() { XCTAssertEqual(2.fizzbuzz, "2") }
    func test3() { XCTAssertEqual(3.fizzbuzz, "Fizz") }
    func test4() { XCTAssertEqual(4.fizzbuzz, "4") }
    func test5() { XCTAssertEqual(5.fizzbuzz, "Buzz") }
}

今度は、Xcodeからテストしなおしてみましょう。⁠Command⁠⁠-⁠U]を押して、図3のとおりに全テスト項目が緑になれば成功です。

図3 Testの実行結果
図3 Testの実行結果

とりあえず最低限文化的なFizzBuzzモジュールができたので、Terminalに戻ってgitリポジトリを作成します。

% git init
% git add .
% git commit -am 'version 0.0.0'
% git tag 0.0.0

これで、公開可能なモジュールが1つできあがりました。

ライブラリを使う――ローカル編

今度はそれをほかから使ってみましょう。

$ cd ../

でFizzBuzzディレクトリを抜けたら、今度はFizzBuzzRunというディレクトリを作成しそこにパッケージを生成します。

$ mkdir FizzBuzzRun
$ cd FizzBuzzRun
$ swift package init --type=executable

--type=executableに注目。デフォルトだとlibraryで、上記のとおりライブラリモジュールが作成されたのですが、今度はそのライブラリを使った実行ファイルを作成するというわけです。

ここで、生成されたPackage.swiftファイルを編集します。これこそがSwift Package Managerの心臓部。空([])だったdependencies:に、

dependencies: [
  .package(url: "../FizzBuzz", from:"0.0.0")
]

を追加し、.targetの中に"FizzBuzz"を追加します。

.target(
            name: "FizzBuzzRun",
            dependencies: ["FizzBuzz"]),

これで、main.swiftimport FizzBuzzする準備が整いました。

Sources/FizzBuzzRun/main.swiftをこう書き換えます。

import FizzBuzz
_ = (1...30).map{ print($0.fizzbuzz) }

あとはswift runするだけです図4⁠。

図4 FizzBuzzの実行
% swift run
Fetching /Users/dankogai/Desktop/swift-38/FizzBuzz
Cloning /Users/dankogai/Desktop/swift-38/FizzBuzz
Resolving /Users/dankogai/Desktop/swift-38/FizzBuzz at 0.0.0
Compile Swift Module 'FizzBuzz' (1 sources)
Compile Swift Module 'FizzBuzzRun' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/FizzBuzzRun
1
2
Fizz
4
Buzz
 ……(中略)……
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz

ライブラリを使う――リモート編

簡単ですね。しかし簡単とは言っても、単にFizzBuzzする程度であればswiftファイル1つあれば事足りますし、必要なことを、すべてそこに書いてもTerminalの画面1つに余裕で収まります。

ではこれをもとにFizzBuzzするWebサーバを作りたいとします。http://localhost:8181/0ならFizzBuzzhttp://localhost:8181/1なら1……程度の簡単なサーバですが、FizzBuzzは簡単でもサーバはそうは行きません。どうしましょう?

Swift Package Managerはまさにそういうときのためにあるのです。まずはFizzBuzzRun同様にFizzBuzzServerという実行ファイルパッケージを生成します。

$ cd ..
$ mkdir FizzBuzzServer
$ cd FizzBuzzServer
$ swift package init --type=executable

そしてPackage.swiftを編集します。

.package(url: "../FizzBuzz", from: "0.0.0"),
.package(url: "https://github.com/
PerfectlySoft/Perfect-HTTPServer.git", from:"3.0.0"),

このhttps://github.com/PerfectlySoft/Perfect-HTTPServer.gitに、Webサーバを実装するのに必要な機能がすべてそろっているわけです。.targetdependencies:にもその旨を追加しておきましょう。

.target(
          name: "FizzBuzzServer",
          dependencies: ["FizzBuzz","PerfectHTTPServer"]),

あとはmain.swiftを適宜書き換えるだけです。あえて説明抜きで全体をリスト3に示します。

リスト3 Sources/FizzBuzzServer/main.swift
import PerfectLib
import PerfectHTTP
import PerfectHTTPServer
import FizzBuzz

var routes = Routes()
routes.add(method: .get, uri: "/**") { q, r in
    if let n = Int(q.path.dropFirst()) {
        r.setBody(string: n.fizzbuzz)
    } else {
        r.status = .notFound
        r.setBody(string: "\(q.path) : not found")
    }
    r.completed()
}

try! HTTPServer.launch(name: "localhost", port: 8181, routes: routes)

Ruby on RailsやPHPのLaravelを使っている人であれば、なんとなくやっていることはおわかりいただけるのではないでしょうか。では実行してみましょう。

swift run
[INFO] Starting HTTP server localhoston :::8181

Port 8181が待ち受け状態になりました。この状態で適当なブラウザでhttp://localhost:8181/0にアクセスしてみましょう。FizzBuzzと表示されましたか?

Swiftからもアクセスしてみましょう。URLにアクセスしてその中身を表示する程度であれば、Swift Package Managerに頼らずともリスト4のような感じで書けます。

リスト4 zzbuzzclient.swift
import Foundation

for n in (1...30) {
    let url = URL(string:"http://localhost:8181/\(n)")!
    let str = try! String(contentsOf:url)
    print(str)
}

そうそう。Webサーバを止めなければいけませんね。通常のシェルプログラム同様、⁠Ctrl⁠⁠-⁠C」で止まります。

次回予告

というわけで駆け足でSwift Package Managerを使ってみました。{cpan,pip,gem,npm}のユーザであれば見よう見まねで使える気がしてきたのではないでしょうか? 次回のWWDC特集をはさんで、次々回以降ふただびSwift Package Managerを取り上げます。

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の実行環境(基礎編)

おすすめ記事

記事・ニュース一覧