てくてくテック

気ままに開発のメモを書いていこうと思います。主にSwiftかと。

iPhoneアプリ開発初心者の友人たちとサンプル集を使った勉強会をしてみます

はじめに

こんにちは。toosaaです。今日は、最近アプリ開発に興味ある友人たちとサンプル集を使った勉強会をやり始めたので、この勉強会についてお話しします。今回は、勉強会の進め方や目的について話し、次回以降で各会の様子について書こうと思います。

メンバーについて

  • アプリ開発経験者
    • toosaaを含めて二人
  • 未経験者
    • 三人

未経験者の三人は、プログラミングの経験はあるが大規模な開発の経験はあまりないという感じです。なお、Swiftについては、皆で「Swift実践入門」を輪講形式で読みあったので、皆最低限のことは理解しています。

勉強会の進め方

毎週1.5hほど集まって、サンプルアプリのソースコードを読むか実装をします。サンプルに関してはTeckAcademyさんがまとめてくれているものから選びます。
今回の勉強会では、いろんな人のいろんな実装をみたり、とにかくたくさん作ってみるという経験を優先したいので、次に記述するように1週目にソースコードを読み、2週目に実装するのを繰り返します。

1週目

やり方

  • サンプルのソースコードを読む(1h)
    • 以下の点についてまとめる
      • 実装のキモ
      • 勉強になった実装
      • 知らなかった機能やClass
      • よくない・直すべき点
  • 読んだ結果を共有する(0.5h)
    • どのような順で読んだか
    • まとめた個所の共有

ねらい

  • 人のコードを読む練習する
    • 単純に勉強になる
    • 今後、あとからプロジェクトに参加するというときに役立つ
  • 精読ではなく多読で経験を積む
    • いろんな人がいろんな書き方をするんだなーというのを実感する
    • 精読に関しては、この勉強会ののちに各自で行えば良い
  • 他の人のコードの読み進め方を知る
    • 普通だと聞かない知識なので、私が聞いてみたいだけです笑
  • 他の人が重要だと思うことの差異や反対に一致する点を知る
    • 他人が気にする点と自分が気にする点をはっきりとさせる

2週目

やり方

  • 1週目で読んだソースと同じ機能を実装する(1h)
    • 参考にしつつ自分なりに書き換えられるとなおよし
  • 実装した結果を共有する(0.5h)
    • どこまで書いたか
    • どやりポイントはどこか
    • 苦労したところはどこか

ねらい

  • 実装の訓練
    • 短い時間という追い詰められた状態でも書く訓練をする
      • そういう力が求められることもあるだろう
    • とりあえず作ってみることを経験する
      • アプリ作りたくなっても、リリースできるようなものは、いきなり作れないし、そもそも思い付かない
      • サンプルアプリを真似ることで、とりあえず作ってみたという経験を積む
    • 自分で組んでみると思わぬところで躓くのをたくさん経験する

終わりに

今回は、勉強会の進め方についてだけ触れました。次回は、1回目の様子についてお話ししたいと思います。

kituraのコードをIBM Cloud(Bluemix)のDevOps経由でdeployする

はじめに

こんにちは。toosaaです。 今回は、Kituraで書いたコードをIBM Cloud(Bluemix)上のホスティングサーバーに、同じくIBM Cloud(Bluemix)のDevOpsを利用して自動deployするやり方について解説します。 ただし、今回はチュートリアルなど公式のドキュメントを元に学んだものではなく、いろいろ試した結果うまくいった方法なので、ご注意ください。

IBM Cloud(Bluemix)にログインする

  • sign upする
    • sign upに必要なものは頑張ってください

ツールチェーンを作成する

  • 左上のハンバーガーメニューでDevOpsを選択する
  • 地域を適当な場所にする
  • 右上のツールチェーンボの作成ボタンを押す
  • Cloud Foundryアプリの開発を選択する
  • Delivery Pipelineを選択する

GitリポジトリをGitLabに指定する

  • ツールの追加でGitLabを追加する

Delivery Pipelineを作成し、自動デプロイできるようにする

  • ツールの追加でDelivery Pipelineを追加する
  • 作成したDelivery Pipelineを選択する
  • ビルドステージを作成する
    • 入力を自身のgitリポジトリにする
    • ジョブを追加する
      • ビルダータイプはシンプル
  • デプロイステージを作成する
    • 入力をビルド成果物にする
    • ジョブをデプロイにする

以上の流れでGitLabにKituraで書いたコードをpushしたら自動でサーバーにデプロイされるようになりました。 urlはデプロイステージの作業完了後にリンクが貼られるので、それを参照してください。

おわりに

今回は、Kituraで書いたコードをIBM Cloud(Bluemix)上に自動デプロイする方法を書きました。基本、用意されたツールを使うだけなのでポチポチすればできます。ただ、GitLabとかに変えようとするとデフォルトでできるリポジトリを消したりと手間がかかります。次回は一旦kituraから離れようかと思います。

Viewを出したり消したりしたい

ゲームの設定画面を作りたい

はじめに

こんにちは、takenoteです。 今回もボードゲームのアプリを作成してみながらSwiftの勉強をしていきます。

どこから手をつけていいものかわかりませんが、 とりあえずゲームプレイ前の初期設定画面の作成を試みます。

今回の内容

以下のような画面の作成を目指します。

f:id:takenote-y:20180212165337p:plain
今回のゴール

この画面の実現のために以下の3つの項目を今回の記事でお話しします。

UIのゴール

 ・人数を決めるためのギミックを用意する

 ・名前を入力するためのCustomViewを作成する

 ・人数に応じてCustomVIewを出したり消したりする

またこの画面において期待される機能は以下のようなものです。

動作のゴール

 ・上にあるPickerViewを回すと画面下部のPlayerName入力用のCustomViewが出たり消えたりする

 ・下にあるGameStart!のボタンを押すと"Player人数", "Playerの名前"の情報が次のViewControllerに引き継がれる

今回は画面遷移のところまではいかないため、動作のゴールとしては一つめのみを記載します。 では本題にいきましょう。

人数を決めるためのギミックを用意する

今回はPlayer人数が2~6人だったこともあり、選択するとプルダウンメニューが出て、人数を選ぶUIがよいのかなーとか思ってました。 ちなみに普段はAndroidユーザーです。

人数を選択するためのUIには様々な種類が存在しているかと思います。僕がパッと思いついたのは以下の3つでした。

 1. プルダウン型の選択UI

 2. ラジオボタン

 3. ロールみたいになってるやつ

あれなんですね、、、iOSって標準ライブラリでプルダウン型のUIもラジオボタンも用意されてないんですね。。。 実現することは普通に可能なようで、少し調べて見たら以下のような記事が出てきました。

qiita.com

dev.classmethod.jp

一方でロールみたいにして選択するあれはPickerViewというもので標準的に搭載されているみたいです。

f:id:takenote-y:20180212164822p:plain
X codeの右下にあるViewを選ぶあそこです

使い方は調べてみるとたくさん出てきますね。

qiita.com

という訳で今回は人数選択にこいつを用いることにします。 人数選択のUIにこだわるのは一周回ってきてデザイン面のブラッシュアップをしたくなった時にすることにします。

ちなみにこのPickerView、本来はいくつか列を持って組み合わせで選択ができるようです。 今現在は人数しか入力させていませんが、特殊ルールやモード選択などができるようになれば、その時にはもっと有効に使える日がくるかもしれません。

名前を入力するためのCustomViewを作成する

以下の方法でLabelとTextViewが横に並んだだけのCustomViewを作成しました。

  1. レイアウトを決めたxibファイルを作成する

 2. 対応するswiftファイルを作成する

 3. StoryBoard上に配置する

基本的には"X code CustomView"で調べるとすぐに実現方法がわかるのですが、 一点だけ見落としてしまい、なかなか前に進むことができないことがありました。

Viewを選択した時に出てくるIdentity Inspectorではなく、File's Ownerを選択した時に出てくるIdentity InspectorのところにあるClassに対応するのところにswiftファイルの名前を入力してください。

f:id:takenote-y:20180212171344p:plain
ここ見落としやすいので気をつけてください

やらしいことにViewを選んでも全く同じような画面が表示されてしまうので、できたできた、と思って次に行ってしまうんですよね。

swiftファイルの中身はこんな感じでとてもシンプルです。

import UIKit

class TestCustomView: UIView {

    @IBOutlet weak var leftLabel: UILabel!
    @IBOutlet weak var textView: UITextField!

    //コードから生成したときに通る初期化処理
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.commonInit()
    }
    
    //InterfaceBulderで配置した場合に通る初期化処理
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.commonInit()
    }
    
    fileprivate func commonInit() {
        //MyCustomView.xibファイルからViewを生成する。
        //File's OwnerはMyCustomViewなのでselfとする。
        guard let view = UINib(nibName: "TestCustomView", bundle: nil).instantiate(withOwner: self, options: nil).first as? UIView else {
            return
        }
        view.frame = self.bounds
        view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        self.addSubview(view)
    }
    
}

人数に応じてCustomVIewを出したり消したりする

「数字を設定するためのPicker」と「出したり消したりするCustomView」の用意が終わりました。 今から用意したCustomViewを出したり消したりします。

customViewの表示を出したり消したりするためには、Viewのもつプロパティであるalphaをいじるようです。 今回はPickerViewの値に応じて数を変更させたかったので、PickerViewのメソッド内に処理を入れています。

class InitialSettingViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource{

    //上のドラムと、その上下に表示する文字Labelです
    @IBOutlet weak var pickerView: UIPickerView!
    @IBOutlet weak var onPickerLabel: UILabel!
    @IBOutlet weak var belowPickerLabel: UILabel!
    @IBOutlet weak var goButton: UIButton!
    
    
    //名前を入力するためのTextViewです。左にあるLabelとセットです。
    @IBOutlet weak var nameInputView0: TestCustomView!
    @IBOutlet weak var nameInputView1: TestCustomView!
    @IBOutlet weak var nameInputView2: TestCustomView!
    @IBOutlet weak var nameInputView3: TestCustomView!
    @IBOutlet weak var nameInputView4: TestCustomView!
    @IBOutlet weak var nameInputView5: TestCustomView!
    
    let numOfPlayer: [[Int]] = [[2,3,4,5,6]] //ドラムに表示する数字です。ドラムは列を増やせるため今後ちょっとしたルールを追加するのにも使えます。
    var inputNameViews: [TestCustomView] = []   //TextViewを一括操作するための配列です
    var defaultNumOfPlayer: Int = 2                            //表示するTextVIewの数の初期値
    
 override func viewDidLoad() {
        super.viewDidLoad()
        goButton.setTitle("Game Start!!", for: .normal)
        onPickerLabel.text = "プレイ人数を選択してください"
        belowPickerLabel.text = "人でプレイ!!"
        inputNameViews = [nameInputView0, nameInputView1, nameInputView2, nameInputView3, nameInputView4, nameInputView5]
        
        var i = 1
        for view in inputNameViews{
            if(i > defaultNumOfPlayer){
                //ドラムで選択された数値以上のTextViewは表示されない。初期設定は2人にしたのでそれ以上のPlayerの入力窓は非表示にした。
                view.alpha = 0
            }
            view.leftLabel.text = "Player" + i.description + ": "
            view.textView.placeholder = "Input your name"
            i += 1
        }
    }

//コンポーネントの個数を返すメソッド
    @IBOutlet weak var numOfPlayersPicker: UIPickerView!
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return numOfPlayer.count
    }

    //コンポーネントに含まれるデータの個数を返すメソッド
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return numOfPlayer[component].count
    }

    //データを返すメソッド
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return numOfPlayer[component][row].description
    }

    //データ選択時の呼び出しメソッド
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        defaultNumOfPlayer= numOfPlayer[component][row]
  //ここで取れた値を元に、CustomViewのalpha値を変更する。
        showOrHideNameInputView(component: component, row: row)
    }
    


 //CustomViewを出したり消したりするmethod
    func showOrHideNameInputView(component: Int, row: Int){
        for i in 0 ..< numOfPlayer[component][row]{
            inputNameViews[i].alpha = 1
        }
        for i in numOfPlayer[component][row] ..< inputNameViews.count{
            inputNameViews[i].alpha = 0
        }
    }
}

これで実装完了です。 ViewControllerを超えた値の持ち越しは次回に回します。

読んでくださってありがとうございました。

ゴキブリポーカーでSwiftの練習

とりあえず挨拶回です

はじめに

こんにちは、takenoteです。toosaaのマブダチです。

最近toosaaと一緒(?)にSwiftの勉強のためにボードゲームを作っています。基本的にProgramingは初心者、かつSwiftも初めて触っているため作成にあたっては何度もつまづいてしまっています。 このブログでは「せっかくなのでやったことをアウトプットしつつ振り返っていこう」ということを目的に、備忘録や自己レビューのために詰まってしまったポイントの「こんな風にしたら解決したよ」を共有させてただきます。

なお作成するボードゲームはオリジナルではなく、既存のものを模倣させていただきます。Swiftの基本文法ですらおぼつかないのに新ゲームなんて作っていたらしっちゃかめっちゃかになってしまうからです。

今回勉強のために作成してみるボードゲームはこちらです。

 ・誰でも理解できるシンプルなルール

 ・登場するアイテムがカードだけ(カウンターやボードなどがない)

 ・ちょうど最近みんなでやって盛り上がった

など、いくつかの点でプログラミングの練習題材としてもってこいだと考えてました。結構有名なゲームなので存在を知っている方も多いのではないでしょうか。 ブログの本題からずれてしまうためゲームの詳細な説明などはしませんが、記事が無事進んでいけば、ルールについて触れることもあるかと思います。 なのでゲームの全貌についてはじわじわ知っていただけるかと思います。すぐに知りたい人は友達を探してLet's Play

ブログの進め方的

平日の朝晩にちょっとずつ作業して、休日にそれをブログに起こせればいいなーと思っています。 なので1週間に一本の記事が目下目標かなというところです。

技術的な記載はほとんどリンクを貼って参考ページを元にしながら、 実際にこんな風に書いて見ました、をどんどん載せていければよいなーと思っています。

初回である今回は「こんな感じに進めていくよ!」を書いて終わりにします。

全体的な構成イメージとか

今回は技術的なことは全く触れずに、これからどんなアプリを作成していこうかの考えだけを書いていこうと思います。 UIはこんな風にしたいよ、とかこういうポリシーで作るよ、とかそんなところです。

全体のUIイメージ

ザクっと以下のような画面構成でアプリを作成しようと考えています。

f:id:takenote-y:20180212142254p:plain
ゲーム画面イメージ

この画面イメージは、アプリ作成にあたっては以下のような項目に気をつけたいと考えたからです。

・常に情報が画面に表示されていて、選択/決断の時にいちいち確認に画面を閉じたり戻ったりしない

・一つのスマートフォンにゲームを入れておけばみんなで楽しめる

一番目を達成するために、真ん中の部分に「他のPlayerとかの情報」を表示する土地を大きくとりました。 また二番目と達成するために「今今どういう状況で何が起こったか」を表示しておく「ゲーム状況のLog」表示用の土地を用意しました。

画面遷移について

ゲーム全体を通して「オープニング画面」⇨「初期設定画面」⇨「ゲーム画面」で終わりにしようと考えています。 またゲームプレイ中の画面の遷移は0にしたいなーと考えています。その心は「常に情報が画面に表示されていて、選択/決断の時にいちいち確認に画面を閉じたり戻ったりしない」に基づいています。

どうやって実現するのかは、またおいおい考えます。

アプリ設計について

設計指針については次回に回そうと思います。 というか考え方がいまいちわかっていません。 あまりこの辺に詳しくないため、使いまわせないクラス構造とか見たことないデザインパターンとかFat ViewControllerとか多発すると思います。

できるだけ考えたことは素直にアウトプットして、後日新しく学んだことは追記する形で自己レビューしていければいいなーと考えています。

以上、とりあえずこんな感じで作りたいな、という項目でした。

終わりに

今までは一人でこそっとアプリの勉強とかしていたので、こんな風に人に公開しながら進めたり 友人と一緒にやったりという体験にとてもワクワクしています。

賢いやり方とかいっぱい勉強できるでしょうか。 自分の成長のために何かためになることを得られればと思っています。

ついでにどなたか皆様のためにもなればこれ幸いです。

とりあえず継続を目標にやっていくのでどうぞよろしくお願いいたします。

Kituraのチュートリアル(ToDoBackend)で気になったことを調べた

はじめに

こんにちは。toosaaです。

前回、Kituraのチュートリアルの一つであるToDoBackendを動かしました。今回は、その中で気になった点について調べます。

Codable プロトコル

Swift4から追加されたプロトコルで、これを使うとjsonとの変換を勝手にやってくれます。すごく便利です参考。 今回のチュートリアルでは、Web APIとしてjsonを返すため利用しているのだと思います。そして、このcodableを使うためkituraのversionは swift4なのだと思います。

Equatable プロトコル

ToDoモデルはCodableプロトコル以外にEquatableプロトコルにも準拠しています。これはKituraとして必要というわけではなく、サンプルのupdateHandler()やdeleteHandler()でArrayの中からオブジェクトを見つけるのに必要だから利用しているようです。let idPosition = todoStore.index(of: idMatch)ここの部分です。

handlerの指定の仕方

チュートリアルだとApplication.swift内にhandlerのメソッドを実装してますが、実運用するにあたってはモデルに紐づけて実装したいです。これは単に、モデルclassにstaticメソッドで同じメソッドを実装すればいいです。とりあえず、データを格納するarray等もstaticにしてしまえば動くものはできます。実際はDBと接続するでしょう。

// Model.swift
extension ToDo{
    static var todoStore = [ToDo]()
    static var nextId :Int = 0
    static let workerQueue = DispatchQueue(label: "worker")
    
    static func storeHandler(todo: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
        var todo = todo
        if todo.completed == nil {
            todo.completed = false
        }
        todo.id = nextId
        todo.url = "http://localhost:8080/\(nextId)"
        nextId += 1
        execute {
            todoStore.append(todo)
        }
        completion(todo, nil)
    }
    
    static func deleteAllHandler(completion: (RequestError?) -> Void ) {
        execute {
            todoStore = [ToDo]()
        }
        completion(nil)
    }
    
    static func getAllHandler(completion: ([ToDo]?, RequestError?) -> Void ) {
        completion(todoStore, nil)
    }
    
    static func getOneHandler(id: Int, completion: (ToDo?, RequestError?) -> Void ) {
        completion(todoStore.first(where: {$0.id == id }), nil)
    }
    
    static func updateHandler(id: Int, new: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
        guard let idMatch = todoStore.first(where: { $0.id == id }),
            let idPosition = todoStore.index(of: idMatch) else { return }
        var current = todoStore[idPosition]
        current.user = new.user ?? current.user
        current.order = new.order ?? current.order
        current.title = new.title ?? current.title
        current.completed = new.completed ?? current.completed
        execute {
            todoStore[idPosition] = current
        }
        completion(todoStore[idPosition], nil)
    }
    
    static func deleteOneHandler(id: Int, completion: (RequestError?) -> Void ) {
        guard let idMatch = todoStore.first(where: { $0.id == id }),
            let idPosition = todoStore.index(of: idMatch) else { return }
        execute {
            todoStore.remove(at: idPosition)
        }
        completion(nil)
    }
    
    static func execute(_ block: (() -> Void)) {
        workerQueue.sync {
            block()
        }
    }
}
// Application.swift
router.get("/", handler: ToDo.getAllHandler)
router.get("/", handler: ToDo.getOneHandler)
router.post("/", handler: ToDo.storeHandler)
router.delete("/", handler: ToDo.deleteAllHandler)
router.delete("/", handler: ToDo.deleteOneHandler)
router.patch("/", handler: ToDo.updateHandler)

終わりに

次は、Bluemix上でこのアプリを動作させる方法について書こうかと思います。

Kituraのチュートリアル(ToDoBackend)を試してみた

はじめに

こんにちは。toosaaです。

前回IBM製のフレームワークKituraのGetting Startedに従ってサーバーサイドのHello, World!を行いました。今回は、Getting Startedに続いてチュートリアルとして用意されているToDoBackendを試してみます。そして次回、その内容の一部を調査しようと思います。

動作環境

  • macOS High Sierra ver 10.13.3
  • Xcode ver 9.2
  • Homebrew 1.5.2

事前準備

ToDoBbackendに対して、todo-backend-js-specによるテストをグリーンにしていくことで開発していきます。テスト駆動ですね。そのため、ToDoBackendとtodo-backend-js-specの二つのプロジェクトを用意します。

  • $ git clone http://github.com/IBM/ToDoBackend
  • $ git clone http://github.com/TodoBackend/todo-backend-js-spec

試しにテストを実行してみる

  • $ cd path/to//todo-backend-js-spec
  • $open index.html
  • 起動したブラウザのページのtest target rootにhttp://localhost:8080をセット
  • run testsをクリック

これでテストを実行できます。おそらく、最初の行にthe api root responds to a GET (i.e. the server is up and accessible, CORS headers are set up)とエラーが出てると思います。サーバーを起動してないのだからエラー出て当然です。

ToDoBackendのサーバーを初期化する

  • $ cd path/to/ToDoBackend
  • $ mkdir ToDoServer
  • $ cd ToDoServer
  • $ kitura init
  • open ToDoServer.xcodeproj
  • ビルドスキームをToDoServerに変更(デフォルトではToDoServer-Packageになっているはず)
    • Xcode左上の停止ボタン(四角いやつ)の右
  • ⌘-Rで実行
  • 再度テストを実行する

おそらくまたthe api root responds to a GET (i.e. the server is up and accessible, CORS headers are set up)とエラーが出てると思います。 Cross Origin Resource Sharing (CORS) が有効になっていないからです。

Cross Origin Resource Sharing (CORS) を有効化する

  • CORSライブラリをPackage.swiftに追加する
    • (注)チュートリアルページではdepenciesの方にConfigurationとかあり、エラーになった
// Package.swift
let package = Package(
    name: "ToDoServer",
    dependencies: [
      .package(url: "https://github.com/IBM-Swift/Kitura.git", .upToNextMinor(from: "2.0.0")),
      .package(url: "https://github.com/IBM-Swift/HeliumLogger.git", .upToNextMinor(from: "1.7.1")),
      .package(url: "https://github.com/IBM-Swift/CloudEnvironment.git", from: "6.0.0"),
      .package(url: "https://github.com/RuntimeTools/SwiftMetrics.git", from: "2.0.0"),
      .package(url: "https://github.com/IBM-Swift/Health.git", from: "0.0.0"),
      //ここ追加
      .package(url: "https://github.com/IBM-Swift/Kitura-CORS", .upToNextMinor(from: "2.0.0")),
    ],
    targets: [
      .target(name: "ToDoServer", dependencies: [ .target(name: "Application"), "Kitura" , "HeliumLogger"]),
      //ここにKituraCORS追加
      .target(name: "Application", dependencies: [ "Kitura", "KituraCORS", "CloudEnvironment", "Health" , "SwiftMetrics",
                                                   ]),
      .testTarget(name: "ApplicationTests" , dependencies: [.target(name: "Application"), "Kitura","HeliumLogger" ])
    ]
)
  • packageをインストールするためにregenerateする
    • Xcodeを閉じる
    • $ cd path/to/ToDoBackend/ToDoServer
    • $ swift package generate-xcodeproj
    • $ open TodoServer.xcodeproj
  • CORSライブラリを使う
    • Sources/Application/Application.swiftを開く
    • import KituraCORSを追記
    • postInit()の中に下記のコードを追加
let options = Options(allowedOrigin: .all)
let cors = CORS(options: options)
router.all("/*", middleware: cors)

この状態で再度テストを行うと、the api root responds to a POST with the todo which was posted to itに変わっているはずです。(注)もし変更なかったら、テスト用のページを一旦戻りrun testsしてみると変わるかと思います。このテスト結果はPostリクエストの処理を実装してないためです。

Postリクエストの処理を実装する

  • Modelを生成する
    • XcodeのProjectNavigator上のApplicationフォルダに右クリック
    • New File...を選択する
    • 名前をModels.swiftにする
    • TargetsをApplicationだけにする(おそらくToDoServerPackageDescriptionにチェックがついている)。
      • 私の環境だと最初はTargetsを選択する領域が出てなかったのですが、何かのボタンを押したら出たと記憶しています
    • Create
    • 下記のコードを追加
public struct ToDo : Codable, Equatable {
    public var id: Int?
    public var user: String?
    public var title: String?
    public var order: Int?
    public var completed: Bool?
    public var url: String?
    
    public static func ==(lhs: ToDo, rhs: ToDo) -> Bool {
        return (lhs.title == rhs.title) && (lhs.user == rhs.user) && (lhs.order == rhs.order) && (lhs.completed == rhs.completed) && (lhs.url == rhs.url) && (lhs.id == rhs.id)
    }
}
  • ToDoアイテムを格納するArrayを作る(このチュートリアルではDBは使わない)

    • Sources/Application/Application.swiftを開く
    • let cloudEnv = CloundEnv()の下に下記のコードを追加する

      private var todoStore = [ToDo]()
      private var nextId :Int = 0
      private let workerQueue = DispatchQueue(label: "worker")
      
    • App Class内に下記メソッドを追加

      func execute(_ block: (() -> Void)) {
         workerQueue.sync {
           block()
         }
      }
      
  • / へのpostリクエストに対応するhandlerを登録する

    • postInit()に下記コードを追加する
router.post("/", handler: storeHandler)
  • App Classに下記のメソッドを追加する
func storeHandler(todo: ToDo, completion: (ToDo?, RequestError?) -> Void ) {
     var todo = todo
     if todo.completed == nil {
         todo.completed = false
     }
     todo.id = nextId
     todo.url = "http://localhost:8080/\(nextId)"
     nextId += 1
     execute {
         todoStore.append(todo)
     }
     completion(todo, nil)
 }

DELETE, GET, PATCHの実装もする

基本は、postと同じ感じなので割愛

終わりに

今回は、Kituraのチュートリアルの一つToDoBackendを試してみました。次回は、この中で調査したものについて書きます。

Kituraを使ってサーバーサイドSwiftを試してみた

はじめに

すごく久しぶりに投稿します。今後は、友人たちと投稿しようと思います。

今回は、Swiftを使ってサーバーサイドを書いてみたいと思い、IBM製フレームワークのKituraを使ってみた件について、toosaaがお話しします。

内容としては、Getting Startedを動かしてみるまでと、ちょっとした調査となります。

動作環境

  • macOS High Sierra ver 10.13.3
  • Xcode ver 9.2
  • Homebrew 1.5.2

Getting Started

Getting Startedの手順に従います。2017年2月3日現在、同じことを書いているだけです。

事前準備

Kitura コマンドラインのインストール

  • $ brew tap ibm-swift/kitura
  • $ brew tap ibm-swift/kitura

プロジェクトの初期化

  • $ HelloKitura
  • $ cd HelloKitura
  • $ kitura init
    • ここにあるように色々なファイルが作られます
      • .gitignoreとかもあります
      • この記事書いている時に、このページにCRUD projectやmodel generatorというのがあるのに気づきました。あとで読みます。

Hello, World!

  • $ open HelloKitura.xcodeproj
  • Sources/Application/Application.swiftpostInit()メソッドの中に下記のコードを挿入

注) 私の環境ではimport Kiture のとこにNo such module 'Kitura'とエラー出ましたが普通にビルドできます。

// Handle HTTP GET requests to /
router.get("/") {
    request, response, next in
    response.send("Hello, World!")
    next()
}
  • ビルドスキームをHelloKituraに変更(デフォルトではHelloKitura-Packageになっているはず)
    • Xcode左上の停止ボタン(四角いやつ)の右
  • ⌘-Rで実行
  • http://localhost:8080 にアクセス

これだけです。

調査

Hello, World!で書いたコードについて調べてみます。

routeの指定

routerはRouter Classのインスタンスで、getのオーバーロードをいくつか持ってます。そのメソッドの宣言を引っ張って来ると下記の通りです。参考

//RouterHTTPVerbs_generated.swift
public func get(_ path: String?=nil, handler: RouterHandler...) -> Router
public func get(_ path: String?=nil, handler: [RouterHandler]) -> Router
public func get(_ path: String?=nil, allowPartialMatch: Bool = true, middleware: RouterMiddleware...) -> Router 
public func get(_ path: String?=nil, allowPartialMatch: Bool = true, middleware: [RouterMiddleware]) -> Router

今回使っているのは、これらの一つですが、下記のメソッドもあります。チュートリアルで使われています。

//CodableRouter.swift
public func get<O: Codable>(_ route: String, handler: @escaping CodableArrayClosure<O>)
public func get<O: Codable>(_ route: String, handler: @escaping SimpleCodableClosure<O>)
public func get<Id: Identifier, O: Codable>(_ route: String, handler: @escaping IdentifierSimpleCodableClosure<Id, O>)
public func get<Q: QueryParams, O: Codable>(_ route: String, handler: @escaping (Q, @escaping CodableArrayResultClosure<O>) -> Void)

上のget()は、クロージャーを使っていますが、@escapingがついていない&返り値があるので同期処理なのだろうと思います。反対に下のは、非同期処理で使うのだろうと思います。あとCodableなのでjsonのシリアライズでシリアライズ。

どちらにせよ、第一引数の文字列がurlに関るはずです。名前がrouteとpathで違うのはわかりませんが。

http responseを返す

Hello, Worldではresponse.send("Hello, World!")でhttp responseを返していると予想できます。このresponseはRouterResponse classのインスタンスです。このclassのsend()は下記がとなっています。参考

public func send(_ str: String) -> RouterResponse
public func send(data: Data) -> RouterResponse
public func send(fileName: String) throws -> RouterResponse
public func send(json: [Any]) throws -> RouterResponse
public func send(json: [String: Any]) throws -> RouterResponse
public func send(status: HTTPStatusCode) -> RouterResponse
public func send<T : Encodable>(_ obj: T) throws -> RouterResponse
public func send<T : Encodable>(json: T) throws -> RouterResponse
public func send<T : Encodable>(jsonp: T, callbackParameter: String = "callback") throws -> RouterResponse

今回は一番最初のstringを返しています。静的なhtmlならsend(fileName: String), web apiならjsonのを使えば良さそうです。

next?

RouterHandlerの第三引数で、@escaping () -> Voidというクロージャーです。ドキュメントでは下記のように書かれています。ちょっと、コードを読んでみましたが、よくわかっていません(・_・;)

The closure to invoke to cause the router to inspect the path in the list of paths.

終わりに

今回は、KituraのGetting Startedと、その中で出てきたコードの簡単な調査を話しました。次回は、チュートリアルの内容に触れようと思います。