始めて見よう! SwiftでWebアプリ開発(基礎編)

  • 36
    いいね
  • 2
    コメント

SwiftのWebフレームワークであるKituraを使ったアプリ開発の始め方をここにまとめおきます。

ソースコードはこちらに置いておりますのでご参考ください。

事前準備

  1. Homebrewのインストール ターミナルで下記のコマンドを実行しインストールします。後続のインストールに必要なパッケージマネージャーをインストールしておきます。
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  1. Curlのインストール APIを叩く時にcurlコマンドは基本なので、下記のコマンドをターミナルで実行してインストールしておきます。
$ brew install curl
  1. Xcodeのインストール 実際にアプリを作成する際にはXcodeが便利。 こちらからXcodeのインストールをしておきます。

Workディレクトリを用意する

ターミナルからディレクトリを作成してカレントディレクトリを移動します。

$ mkdir myToDoList
$ cd myToDoList

Swift package Managerを使ってInitialize

Swift Package Managerを使って開発を行うための様々なライブラリをダウンロードし、依存関係も含めて用意してくれます。cocoapodsなどはiOS開発では使われていますが、サーバーサイドではおそらくこちらが主流となってくるかと思います。

$ swift package init

インストールされたライブラリを見てみます。

$ ls
Package.swift   Sources     Tests

ちなみに Package ManagerのHelpはこのコマンドで確認できるので紹介しておきます。

$ swift package init --help

Xcodeプロジェクトを起ち上げる

以下のコマンドでXcodeプロジェクトを生成する。

$ swift package generate-xcodeproj

Xcodeプロジェクトを立ち上げます。

$ open myToDoList.xcodeproj/

立ち上げると以下のような基本的なプロジェクト構造を持っていることがわかります。

.
├─ ─ Package.swift
├─ ─ Sources
│    └── myToDoList.swift
├─ ─ Tests
│    └── myToDoListTests
│    └── myToDoListTests.swift
└─ ─ myToDoList.xcodeproj

Kituraフレームワークを適用する

まずはKituraライブラリをインストールするために、Kitura Package Catalogにあるライブラリが必要となります。

・・・この鳥ちゃんがかわいいですよね・・・。
swift1.png

ここで、検索バーでKituraといれるとKituraのフレームワークがみつかります。ひとまずこのライブラリのurlを右側のメニューからクリップにコピーします。
Swift2.png

次にpackage.swiftを編集します。以下のようにコピーしたKituraライブラリのurlを貼り付けて、dependenciesを追加します。

package.swift


import PackageDescription

let package = Package(
    name: "myToDoList",
    dependencies: [
      .Package(url: "https://github.com/IBM-Swift/Kitura", majorVersion: 1, minor: 6),
  ]
)

さらに、ログを取得するるためのフレームワークであるHeliumLoggerも同様にKitura Package Catalogから探してpackage.swiftに追加します。

package.swift


import PackageDescription

let package = Package(
    name: "myToDoList",
    dependencies: [
      .Package(url: "https://github.com/IBM-Swift/Kitura", majorVersion: 1, minor: 6),
      .Package(url: "https://github.com/IBM-Swift/HeliumLogger", majorVersion: 1, minor: 6)
  ]
)

Kitura フレームワークを適用する際のアプリ構造

Kituraを使ってWebAPIを構築する際、Routerクラスを生成する必要があります。このRouterクラスはアプリケーション構造のトップレベルでは使用できません。そのため、main.swiftとその従属するライブラリに分けてRouterクラスを使用します。そこでここでは、以下のようなアプリ構造にしていこうと思います。

.
├─ ─ Package.swift
├─ ─ Sources
├─ ─ ├─Server
     └── main.swift
├─ ─ ├─myToDoList
     └── myToDoList.swift
├─・・・

依存関係とpackage.swiftの書き方

以下にソースコードと依存関係を表記します。この依存関係に則って、package.swiftのターゲットを記す必要があります。

ソースの場所 依存関係
Sources/A/main.swift (which requires B)
Sources/B/WebController.swift (which requires C)
Sources/C/DatabaseConnector.swift

例:


import PackageDescription let package = Package(
        name: "MyWebApp",
        targets: [
Target(name: "A", dependencies: [.Target(name: "B")]),
Target(name: "B", dependencies: [.Target(name: "C")]),
Target(name: "C")]

この依存関係に従ってターゲットを以下のように編集します。


import PackageDescription

let package = Package(
  name: "myToDoList",
  targets: [
    Target(name: "Server", dependencies: [.Target(name: "myToDoList")]),
    Target(name: "myToDoList")
  ],
  dependencies: [
    .Package(url: "https://github.com/IBM-Swift/Kitura", majorVersion: 1, minor: 6),
    .Package(url: "https://github.com/IBM-Swift/HeliumLogger", majorVersion: 1, minor: 6)
  ]
)

ライブラリ構成を変更する

現在のライブラリの構成は以下の通りとなっています。

.
├─ ─ Package.swift
├─ ─ Sources
     └── myToDoList.swift
├─・・・

Finderからフォルダを作成して以下の通りにします。

.
├─ ─ Package.swift
├─ ─ Sources
├─ ─ ├─Server
├─ ─ ├─myToDoList
     └── myToDoList.swift
├─・・・

ここで、メインのロジックを書くためのSources/Server/main.swiftを準備します。

$ touch Sources/Server/main.swift

これでライブラリ構成が整ったので、プロジェクトを再度生成し直しすためターミナルからregenerateします。

$ swift package generate-xcodeproj

Sourcesフォルダにライブラリが増えていることがわかると思います。

メインロジックの実装でHello World!

それではさっそく、myToDoListにあるコードを全選択して、カットしてください。
それをそのままmain.swiftに貼り付けます。

main.swift


import Kitura

let router = Router()

router.get("/") {
  request, response, next in
  response.status(.OK).send("Hello Wolrd")
  next()
}

Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()

シミュレーターをサーバーに変更してBuildが成功したら、ブラウザを立ち上げます。

さっそくhttp://localhost:8090にアクセスしてみましょう。

おめでとうございます!Hello Worldが見えましたね!

LOGGERの実装

次にHeliumLoggerを実装します。

main.swift


import Kitura
import HeliumLogger
import LoggerAPI

let router = Router()

HeliumLogger.use()

router.get("/") {
  request, response, next in
  Log.info("Hello!Logger!")
  response.status(.OK).send("Hello Wolrd")
  next()
}

Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()

Buildして実行後、ターミナルからcurlコマンドを叩いてみます。

curl http://localhost:8090

Xcodeのコンソール上に以下のようなメッセージが出てきていれば問題ありません。

[2017-03-06T23:13:52.830+09:00] [VERBOSE] [HTTPServerRequest.swift:215 parsingCompleted()] HTTP request from=127.0.0.1; proto=http;
[2017-03-06T23:13:52.830+09:00] [INFO] [main.swift:11 Server] Hello!Logger!

ライブラリーを使ってmain.swiftを洗練する

main.swiftを軽くし、ライブラリ内の閉じた世界でテストできるように構成を変更します。
RouterをmyToDoListに持っていくと以下のようにスッキリさせます。

main.swift


import Kitura
import LoggerAPI

import myToDoList


HeliumLogger.use()

Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()

myToDoList.swift


import Kitura
import LoggerAPI

let router = Router()

router.get("/") {
  request, response, next in
  Log.info("Hello!Logger!")
  response.status(.OK).send("Hello Wolrd")
  next()
}

ここでHTTPServerとmyToDoListでLinkageなどが貼られていないので、Classを作成します。
Routerをhandlerを使ってAPIとして読み込めるようにしていきます。
ここではGetとPostを作っていくので以下のようにしました。


import Kitura
import LoggerAPI

public class myToDoList {
  public let router = Router()
  public init() {
    router.get("/v1/tasks", handler: handleGetTasks)
    router.post("/v1/tasks", handler: handleAddTask)
  }
}

extension myToDoList {

  func handleGetTasks(request: RouterRequest,
                      response: RouterResponse,
                      next: @escaping() -> Void) throws
  {
  Log.info("Show my tasks!")
  response.status(.OK).send("Getting tasks!")
  next()
  }

func handleAddTask(request: RouterRequest,
                    response: RouterResponse,
                    next: @escaping() -> Void) throws
  {
  Log.info("Adding a task!")
  response.status(.OK).send("Added a task!")
  next()
  }
}

main.swiftからこのクラスを呼び出して使います。

main.swift


import Kitura
import LoggerAPI
import HeliumLogger
import myToDoList

HeliumLogger.use()

let mytodolist = myToDoList()

Kitura.addHTTPServer(onPort: 8090, with: mytodolist.router)
Kitura.run()

だいぶスッキリしました。

Curlで呼び出してみる

$ curl -X GET localhost:8090/v1/tasks
Getting tasks!
$ curl -X POST localhost:8090/v1/tasks
Added a task!

データの配列を用意する

ローカルの配列にタスクを入れてToDoのやり取りをしてみます。
まずは、SwiftyJSONのインポートやtasksの配列の準備、初期値として一つタスクを要素として入れておきます。

myToDoList.swift


import Kitura
import LoggerAPI
import SwiftyJSON

public class myToDoList {
  public let router = Router()
  var tasks: [String] = []
  public init() {
    router.get("/v1/tasks", handler: handleGetTasks)
    router.post("/v1/tasks", handler: handleAddTask)
    tasks.append("牛乳を買う")
  }
}

extension myToDoList {

  func handleGetTasks(request: RouterRequest,
                      response: RouterResponse,
                      next: @escaping() -> Void) throws
  {
  Log.info("Show my tasks!")
    response.status(.OK).send(json: JSON(tasks))
  next()
  }

func handleAddTask(request: RouterRequest,
                    response: RouterResponse,
                    next: @escaping() -> Void) throws
  {
  Log.info("Adding a task!")
  response.status(.OK).send("Added a task!")
  next()
  }
}

この状態でGETを投げてみます。JSONで返ってくるはずです。

curl -X GET localhost:8090/v1/tasks
[
 “牛乳を買う”
]

タスクが返ってきました。

データを配列に格納する

次にbodyparserのプラグインを実装します。

myToDoList.swift


・・・
public class myToDoList {
  public let router = Router()
  var tasks: [String] = []
  public init() {
    router.all("*", middleware: BodyParser())
    router.get("/v1/tasks", handler: handleGetTasks)
    router.post("/v1/tasks", handler: handleAddTask)
    tasks.append("牛乳を買う")
  }
}
・・・

レスポンスの処理を記載します。

myToDoList.swift


・・・
func handleAddTask(request: RouterRequest,
                    response: RouterResponse,
                    next: @escaping() -> Void) throws
  {
    guard let body = request.body else {
      return
    }
    guard case let .json(json) = body else {
      return
    }
  Log.info("Adding a task!")
・・・

ここで、返ってくるpayloadに対する処理をextentionを使って整理します。

myToDoListに新しいファイルを作成します。
swift5.png

extentionを使って以下のように書き直します。

RouterRequestExtension.swift


import Foundation
import Kitura
import SwiftyJSON

extension RouterRequest {
  var json: JSON? {
    guard let body = self.body else {
      return nil
    }
    guard case let .json(json) = body else {
      return nil
    }
    return json
  } 
}

myToDoListも変更します。ここではレスポンスがjsonで返って来なかった場合のエラー処理を施します。

myToDoList.swift


・・・
func handleAddTask(request: RouterRequest,
                    response: RouterResponse,
                    next: @escaping() -> Void) throws
  {
    guard let json = request.json else {
      response.status(.badRequest)
      next()
      return
    }
・・・

排他制御

get処理もadd処理も非同期で行われるので整合性を保つために排他制御を実装し同期処理にします。
Dispatchをimportし、queueを用意します。

myToDoList.swift


import Kitura
import LoggerAPI
import SwiftyJSON
import Dispatch

public class myToDoList {
  public let router = Router()
  var tasks: [String] = []
  let queue = DispatchQueue(label: "com.expample.mytodo")
  public init() {
    router.all("*", middleware: BodyParser())

・・・・

AddTaskににJSONの同期処理を施します。

myToDoList.swift


・・・
    let description = json["description"].stringValue
    queue.sync {
      tasks.append(description)
      Log.info("Adding a task!")
      response.status(.OK).send("Added a task!")
      next()
    }

処理の確認

まずはGETで確認

$ curl-X GET http://localhost:8090/v1/tasks
[
  "牛乳を買う"
]

次にPOSTしてToDoを追加してみます。

$ curl -H "Content-type: application/json" -X POST -d '{"description":"郵便局に行く"}' http://localhost:8090/v1/tasks
Added a task!

GETして確認してみましょう。

$ curl-X GET http://localhost:8090/v1/tasks
[
  "牛乳を買う",
  "郵便局に行く"
]

いかがでしたでしょうか?WebAPIもSwitで書けるなんて、これからのSwitの発展に益々期待が持てちゃいます!

次回はDBとの連携やMobileとの連携について書いていきたいと思います。

ソースコードはこちらに置いておりますのでご参考ください。