20191204のSwiftに関する記事は20件です。

[Swift] Connectionの概要・使い方まとめ

自己紹介的な

Swift歴2日目の超初級者。
本記事では学習したことをアウトプットして、自分の中で定着させることを目的としています。
初学者のスタートダッシュの手助けになれば幸いです。

Connectionについて

UI部品とコードを関連付ける際に、コード上でUI部品にどのような役割を持たせるか指定することができる。
具体的には、ConnectionにOutletかActionを設定することで指定できる。

OutletとActionって何ぞや?

OutletとActionはどのように使い分ける?
色々調べたことをまとめると、
・Outlet → そのUI部品をどこかで参照したいとき
・Action → そのUI部品の動作内容を決めたいとき

例えば、buttonを押したらlabelに"Hello World"が出力される場合を考える。
buttonはlabelを参照して"Hello World"を代入する、つまりUI部品の動作内容を決めているのでAction。
labelは、buttonから参照されているのでOutlet。

おわりに

もし間違っていたらご指摘頂けると助かります。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIViewControllerのアニメーション無しpresent遷移

はじめに

普段、IOSのアプリ開発をしているゆーすけと言います。
これから備忘録も兼ねてQiitaの記事を投稿していこうと思います!
あまり投稿は慣れてないので多少見辛いかもしれませんが
よろしくお願いします。

UIViewControllerのアニメーション無しpresent遷移

現在新規アプリの開発に携わっているのですが、
アニメーション無しでViewControllerの遷移をする必要がありました。
今までアニメーション無しの遷移なんてなかなか使ってなかったので
直ぐには方法が思いつきませんでした。

実装

FirstViewController.swift
   let secondViewController = UIViewController()
   self.present(secondViewController, animated: false, completion: nil)

ポイントはanimatedをfalseにすることです。
falseにすることでアニメーション無しで遷移できます。

普段、何も意識せずanimatedはtrueにしていたので、
それぞれの引数がどんな役割をしているのか、もっと意識して実装しないとなと
つくづく感じました。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FirestoreのArrayContainAnyクエリでタイムラインを実装してみた

 はじめに

初アドベントカレンダー!!

普段はiOS開発のインターンをしながら、個人でコツコツアプリを作ったりしております。

今回はFirestoreのアップデートでArrayContainAnyというQueryが追加され、どうやらタイムライン実装で活躍してくれそうで、自分なりにやってみたので記事にしてみました。

※ 間違えている点や、改善点などございましたらコメントかTwitter(https://twitter.com/yuto_nakano44)
にご連絡いただければ幸いです

ArrayContainAnyクエリとは

こちらの記事がとても分かりやすかったので詳しくはこちらをみていただけると!↓
https://medium.com/anti-pattern-engineering/待ちに待ったfirestoreにarray-contains-anyとin検索がキタ-1771cbc14724

使い方としては、newsコレクションのsports配列にbasketBallが要素として含まれているドキュメントが欲しい!
となれば下記のようになります↓

collection("news").whereField("sports", arrayContainsAny: ["basketball"])

basketballだけでなく、baseballが要素として含まれているドキュメントが欲しい!
となれば下記のようになります↓

collection("news").whereField("sports", arrayContainsAny: ["basketBall", "baseball"])

タイムライン機能実装

現在、個人でアプリを作っており、その過程で以前シンプルなタイムライン機能を実装しました。

タイムラインを構成するデータとしては下記の通りです。
・users (ユーザーのプロフィール情報が保存されているコレクション)
・posts (全ユーザーの投稿情報が保存されているコレクション)

IMG_0144.jpg

実装方法

コレクションのデータです↓

postsコレクション

posts - id
     - title
     - content
     - reply_count

usersコレクション

users - id
      - name
      - user_icon
      - posts <- userが投稿したpostを格納した配列

手順としては

1, postsを取得
2, posts一つ一つのidを配列に格納
3, arrayContainsAnyクエリでpostのidにヒットしたusersのドキュメントを取得

コード

    func fetchUser(posts: [Post], completion: @escaping ((Result<[User], Error>) -> Void)) {
        // postのidを配列に格納
        let postIds = posts.map { (post) -> String in
            return post.id
        }                              // usersコレクションのposts配列にpostIds内のidが含まれていたら
        Firestore.firestore().collection("users").whereField("posts", arrayContainsAny: postIds)
            .addSnapshotListener({ (snapshot, error) in
                guard let snapshot = snapshot else {
                    return
                }

                let users = snapshot.documents.map { (user) -> User in
                    return try! Firestore.Decoder().decode(User.self, from: user.data())
                }
                completion(.success(users))
            })
        }

ユーザー取得できた?

※クエリの配列(今回で言うpostIds)には10個の要素までしか配列に格納できず、
10個より多いと以下のエラーが出てクラッシュします。↓

"Invalid Query. 'arrayContainsAny' filters support a maximum of 10 elements in the value array."

配列の要素が空の場合も↓

Invalid Query. A non-empty array is required for 'arrayContainsAny' filters.

ログでしっかりエラーの原因を出してくれるとありがたいですね。

今回の方法でタイムラインを実装するなら10件までしか一度に表示できないですね。
必要なタイミングで都度データを取得する必要がありそうです。

終わりに

自分なりにタイムラインの実装をやってみたので記事にしてみました。
Firestoreのデータ構造について勉強しなければ。
フィードバックいただけると嬉しいです!
ありがとうございました。

参考: https://medium.com/anti-pattern-engineering/待ちに待ったfirestoreにarray-contains-anyとin検索がキタ-1771cbc14724

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift Network.framework Study 20191204「UDP」

Study

Network.framework
Study:Client側

環境

Client:Swift、Xcode
Server:Java、NetBeans

Client Source Swift

SwiftはNWConnectionに「NWParameters.udp」を設定するだけ。

import Foundation
import Network

var running = true

func startConnection() {
    let myQueue = DispatchQueue(label: "ExampleNetwork")
    let connection = NWConnection(host: "localhost", port: 7777, using: NWParameters.udp)
    connection.stateUpdateHandler = { (newState) in
        switch(newState) {
        case .ready:
            print("ready")
            sendMessage(connection)
        case .waiting(let error):
            print("waiting")
            print(error)
        case .failed(let error):
            print("failed")
            print(error)
        default:
            print("defaults")
            break
        }
    }
    connection.start(queue: myQueue)
}

func sendMessage(_ connection: NWConnection) {
    let data = "Example Send Data".data(using: .utf8)
    let completion = NWConnection.SendCompletion.contentProcessed { (error: NWError?) in
        print("送信完了")
        running = false
    }
    connection.send(content: data, completion: completion)
}

startConnection()

//dispatchMain()

while running {
    sleep(1)
}

Server Source Java

JavaはTCPとUPDで使用するクラスが違う。

package example.java.network;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ExampleServerUDP {
    private static final int EXHOMAX = 255;

    public static void main(String[] args) {
        try {
            DatagramSocket datagramSocket = new DatagramSocket(7777);
            DatagramPacket datagramPacket = new DatagramPacket(new byte[EXHOMAX], EXHOMAX);
            while(true) {
                datagramSocket.receive(datagramPacket);
                for(int i = 0; i < datagramPacket.getLength(); i++) {
                    System.out.print(datagramPacket.getData()[i]);
                    System.out.print(" ");
                }
                System.out.println();
                datagramPacket.setLength(EXHOMAX);
            }
        }
        catch(Exception e) {
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Makefileを利用してiOS開発を賢く便利に運用しよう?

はじめに

VALU Advent Calendar 2019 4日目の記事です!
VALU 社として記事を書きたい! と名乗りを上げて2年目になりました。ことしもよろしくおねがいします!

今回は「Makefile を利用して iOS 開発を賢く運用しよう」ということで,Makefile の補完機能を用いまして,チームメンバーが快適に,かつ本質に集中できる環境を維持することのできる試みのご紹介です。

目次

Makefile との出会い

初めて Makefile と出会ったのは,そうですね。もともとは組み込み寄りであった自身の弱点を克服するため,gohome という Go 製の API サーバーを作成していた最中の出来事でした。

Go 言語自体も初めてであったことで色々調べているうちに Golang を使うなら Makefile を恐れるな という記事に出会いました。

本来の Makefile の使い方とは異なりますが,Makefile を作成し,よく使うコマンドを : 区切りの Key とスクリプトで構成する。ターミナルから $ make build などと入力してスクリプトを実行させると便利だよ,という記事です。

Makefile の例。画像は YutoMizutani/gohome/Makefile から一部を抜粋

わたしはこれを読み大変感動しまして,ぜひ iOS にも導入しようと決意しました。

Makefile によるスクリプト管理は何が嬉しいのか

環境構築を一括で管理できる

Makefile を利用することで,個々のコマンド操作を統制することが可能です。
最も恩恵を得られるのは新しい人が参入した際の環境構築だと思います。

あなたの README.md はどうでしょうか?「CocoaPods のインストール方法は...」等の長い文章で書かれていませんか? 利用されているツールしか書かれていない場合もあるかと思います。
新しい開発者の目的は,「そのリポジトリがどう動いているかを知ること」より「目的の機能または問題に着手すること」です。

フルスタックエンジニアが助けに来たとしても,1日を環境構築で終えるような組織ではとてももったいないですよね。

VALU では $ make all のみで環境構築が完了します:sparkles::sparkles:

補完が効く

zsh-completions などの補完用プラグインを用いることで,どんなコマンドがあるかを実行前に確認することができます。
これが ShellScript だけでなく Makefile を利用する利点です。

screenshot 452.png

Branch 間の環境変化にも常に同じコマンドで対応できる

「今回新しく CocoaPods を Homebrew から Gem を利用するように変えました! 以降開発者はこのコマンドを利用してください! 古いコマンドは使わないでください!」

という初心者が泣いてしまう呪文を Makefile は解決します。

Makefile 内にスクリプトを書いてコマンドとスクリプトを分離させることで変更に強くなります。iOS 風に言うと protocol 経由でアクセスする,と言えばイメージがしやすいですかね。

呼ぶ側は常に make コマンドから。 $ make install と呼ぶことで環境が変わってもインストール作業が継続できます。

自動環境構築の肝,自動インストール

Homebrew がない? インストールします。 CocoaPods ももちろん。Ruby のバージョンが違う? rbenv を利用するように変更します,というように存在しない場合にも自動でインストールさせるようにしています。

最近「おま環」という言葉を知りました。OSS だとそうもいきませんが,会社PCの社内プロジェクトなので,必要なものは自動で構築させる方が効率的です。

以下は Xcode のバージョンを指定するスクリプトです。CocoaPods 等の Ruby 製ツールは,VALU では Gemfile を用いて管理しています。
各スクリプトは,そのコマンドの実行前に type gem contents mint list | grep -s 等を用いて目的のコマンドが存在するかを判定し,存在しない場合にインストールスクリプトが走るようにしています。
$(dirname $0)/../gems/bundle-install.sh の先では, bundle コマンドが存在するかを判定しており,必要に応じて Ruby 自体がインストールされているかまで遡り,対応が必要なPCにのみ適切にインストールが走るようになっています。

このような綺麗なスクリプトも $ sh select-xcode-version.sh と長ければ全メンバーに実行してもらえない可能性があります。sh って打てば補完されるだろう,というのは「おま環」でした。
make にコマンドを集めておき,短いコマンドで呼び出せるようにしておくことでスクリプトとの距離が縮まります! すてきです!

select-xcode-version.sh
#!/bin/sh

# Install Gems
if !(gem contents xcode-install > /dev/null 2>&1); then
    sh $(dirname $0)/../gems/bundle-install.sh
fi

# Check argv
if [ $# -ne 1 ]; then
    echo "select-xcode-version.sh: error: Required the version" 1>&2
    exit 1
fi

# xcode-install says `Xcode VER.SION.NUM.BER`
XCVERSION_OUTPUT_PREFIX="Xcode "

EXPECT_XCODE_VERSION=$1
CURRENT_XCODE_VERSION=`xcversion selected | grep -i $XCVERSION_OUTPUT_PREFIX | tr -d $XCVERSION_OUTPUT_PREFIX`

# Select Xcode version if these are different
if test $EXPECT_XCODE_VERSION != $CURRENT_XCODE_VERSION; then
    bundle exec xcversion install $EXPECT_XCODE_VERSION ; :
    bundle exec xcversion select $EXPECT_XCODE_VERSION
fi

iOS で実際に利用している Makefile の中身

Swift も LLVM を利用している言語ですが,Xcode によってコンパイル周りのオプションは肩代わりすることができています。
Swift 自体のビルドに必要なすごくながいコマンド とは異なり,とてもシンプルに記述することができます。
あくまで Makefile を使おうというものなので,おまじないや固有表現を避け,ShellScript が読める人なら内容が読めるような形に抑えています。
加えて,具体的なコマンドは環境に依存するパスおよび拡張子以外は別の ShellScript としてファイルを分けており,CI 時に必要なスクリプトは個々に実行できる環境を作り上げています。

こちらの Makefile は「人間がコマンドを覚えず,内容を知らなくとも実現したいことを実現する」という方針で設計しており,
利用者は「とりあえず $ make を打ってみて,補完からデプロイやビルドなどのしたいことを探す」
成長したい人は「Makefile を覗いてみて,必要に応じて質問や修正,追加を行う」
ことで業務の効率化を実現しています。

これにより,QA チームへの環境構築や作業依頼についても「$ make clean をしてみてください」と Slack や口頭で伝達することができるようになっています:tada:

Makefile
# Paths
PROJECT_PATH=./

# File extensions
PROJECT_EXTENSION=.xcodeproj
WORKSPACE_EXTENSION=.xcworkspace

# Definition
PROJECT_NAME=`find $(PROJECT_PATH) -maxdepth 1 -mindepth 1 -iname "*$(PROJECT_EXTENSION)" | xargs -L 1 -I {} basename "{}" $(PROJECT_EXTENSION)`
PROJECT=$(PROJECT_PATH)$(PROJECT_NAME)$(PROJECT_EXTENSION)
WORKSPACE=$(PROJECT_PATH)$(PROJECT_NAME)$(WORKSPACE_EXTENSION)
XCODE_VERSION=`cat $(PROJECT_PATH).xcode-version`

# Xcode を開く
open:
    sh scripts/general/xcode/open-xcode.sh $(WORKSPACE)
# Xcode を強制終了させる
kill:
    sh scripts/general/xcode/kill-xcode.sh


# 一括で環境構築を行う
all:
    make config
    make select
    make generate
    make install
    make sort
    make clean

# ~~~ 省略 ~~~

# ビルドに必要なファイルを生成する
generate:
    make generate-xcodeproj
generate-xcodeproj:
    sh scripts/general/xcodegen/generate-xcodeproj.sh

# 依存するツールやライブラリをインストールする
install:
    make install-mint
    make install-gems
    make install-pods
install-mint:
    sh scripts/general/mint/mint-install.sh
install-gems:
    sh scripts/general/gems/bundle-install.sh
install-pods:
    sh scripts/general/cocoapods/pod-install.sh

余談ですが,この $ make all 内では複雑なスクリプトを隠蔽しています。
上記の make config 内では,post-checkout の Git hook に紐づけてインストール (bootstrap) を入れています。
発生し得るトラブルには事前に対応しましょう。
「ブランチを変えたら (ライブラリのバージョンが変わったために) ビルドできなくなった:cry:」という苦言に毎回対応するのも,そのためのドキュメントを保守するのもつらいです。

Makefile に置くべき便利なワンライナー

この程度,.bashrc に書いておけという意見も分かります。
一方で,リポジトリで共有するということは「あの人の便利な技」チーム全体がその commit から便利になるということです。
何を書いたら良いか分からないという方へ! まずこれを入れて幸せになりましょう!

ディレクトリ配下の Xcode workspace を検索して起動する

open コマンドは macOS で default で指定されたアプリを用いて起動させるコマンドです。実際にはプロジェクトディレクトリや xcworkspace の拡張子を変数に切り出していますが, xcworkspace を利用していることが分かっている場合は,より短いコマンドでターミナルから Xcode を開くことが可能です。

ターミナル
$ open `find . -maxdepth 1 -mindepth 1 -iname "*.xcworkspace"`

-a オプションによって指定したアプリを用いて開くことも可能です。複数の Xcode をインストールしている場合,xcode-select で指定されている PATH を利用して起動させることもできます。

PROJECT_PATH=`find . -maxdepth 1 -mindepth 1 -iname "*.xcworkspace"`
XCODE=`xcode-select --print-path | awk 'match($0, /^.*.app/){ print substr($0, RSTART, RLENGTH) }'`
open -a $XCODE $PROJECT_PATH

キャッシュと Derived Data の削除

よく使うキャッシュと Derived Data の削除を行うワンライナーです。; : と最後に付けることで rm 時に Fail しないようにしています。
われわれは記憶のテストをしているわけではないので DerivedData の場所 ( ~/Library/Developer/Xcode/DerivedData ) は暗記するよりどこかに書いておきましょう。

ターミナル
$ xcodebuild -alltargets clean ; rm -rf ~/Library/Developer/Xcode/DerivedData/ ; :

Xcode project をソートする

有名な Xcode のソートスクリプト をありがたく利用させていただくワンライナーです。Perl ファイルなので curl でお借りして使い終わったら破棄するだけです。
Makefile 内に書いておくことで,常に綺麗な状態で開発を始めることができて便利です。

ターミナル
$ curl -sS https://raw.githubusercontent.com/WebKit/webkit/master/Tools/Scripts/sort-Xcode-project-file \
    > ./script.pl \
    && perl ./script.pl `find . -maxdepth 1 -mindepth 1 -iname "*.xcodeproj"` \
    && rm -f ./script.pl

おわりに

これらは別のリポジトリとして GitHub に公開した方が良いのでは?という声もありました (失念しましたが,実際別リポジトリからスクリプトを叩けるものを最近見かけましたね)。
一方で,ブランチを分けることによって考慮しなければならない (e.g. Xcode のバージョン更新時に一部スクリプトは利用できるが,これは巻き戻さないといけない) 等の問題が発生するためにやめました。Makefile およびスクリプトを一人で書いているので......:innocent:

Alfred を利用してあれこれする便利ですが,簡単な操作はターミナルで完結させるのが良いです。ターミナルって結構便利なんですよ,ということが少しでも伝われば幸いです。難しいところは強い人にやってもらって,その恩恵を慣れていない人にも受けられるようにする。みんながハッピーになるといいな。

VALU Advent Calendar 2019,明日は弊社 CTO @mito_memel さんのすてきなサーバーサイドのお話です。

References

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xcode11.1のローカライズではめられた!

はじめに

Xcode10系から11系にあげてしばらくしてから下記のような画面を見つけた。

en_bug

??日本語と英語混じってる:scream_cat:

現象

なんか同じ画面なのに一部だけローカライズできていない。

理想は下記のような表示。

日本語版 英語版
ja en

Storyboardのローカライズがおかしい?ローカライズファイルは下記のようなもの

Main.strings(Japanese)
/* Class = "UILabel"; text = "かすたむ"; ObjectID = "3Iv-wk-3sA"; */
"3Iv-wk-3sA.text" = "かすたむ";

/* Class = "UINavigationItem"; title = "テスト"; ObjectID = "7pt-6w-M5O"; */
"7pt-6w-M5O.title" = "テスト";

/* Class = "UILabel"; text = "でぃてぃーる"; ObjectID = "DV3-DC-Qdc"; */
"DV3-DC-Qdc.text" = "でぃてぃーる";
.
.
.
/* Class = "UILabel"; text = "フッター"; ObjectID = "vyK-5m-pLY"; */
"vyK-5m-pLY.text" = "フッター";
Main.strings(English)
/* Class = "UILabel"; text = "かすたむ"; ObjectID = "3Iv-wk-3sA"; */
"3Iv-wk-3sA.text" = "Custom";

/* Class = "UINavigationItem"; title = "テスト"; ObjectID = "7pt-6w-M5O"; */
"7pt-6w-M5O.title" = "Test";

/* Class = "UILabel"; text = "でぃてぃーる"; ObjectID = "DV3-DC-Qdc"; */
"DV3-DC-Qdc.text" = "Detail";
.
.
.
/* Class = "UILabel"; text = "フッター"; ObjectID = "vyK-5m-pLY"; */
"vyK-5m-pLY.text" = "Footer";

なんか static cell の画面だけおかしい:question:

調査

パーツのObjectIDを確認

よくわからないけどなんかのタイミングで ObjectID 変わったのかな?と思い確認してみました。

object_id

とくに問題なし:neutral_face:

ローカライズファイルを入れ直してみる

よくわからないけどとりあえずローカライズのチェックを入れ直してファイルを再生成してみました。

localize

変化なし:neutral_face:

Xcodeのリリースノートを確認

しばらく悩んでXcodeのリリースノートをみてみました。

発見:heart_eyes_cat:

UITableViewCell labels in storyboards and XIB files do not use localized string values from the strings file at runtime. (52839404)

どうやら UITableViewCelllabel がローカライズできないようです。
TableViewContentStatic Cells にして CellStyle が下記いずれかの場合に起こるようです。

  • Basic
  • Right Detail
  • Left Detail
  • Subtitle

(最初のスクショは上から Basic, Right Detail, Left Detail, Subtitle, Custom のセルです)

対応

Xcodeのバグなのでリリースを待てばいいのですが、アプリの提出期限もあり下記のような暫定対応をしました。

1 TMPLocalizable.strings ファイルを作る

TMPLocalizable.strings(Japanese)
"かすたむ" = "かすたむ";
"テスト" = "テスト";
"でぃてぃーる" = "でぃてぃーる";
.
.
.
"フッター" = "フッター";
TMPLocalizable.strings(English)
"かすたむ" = "Custom";
"テスト" = "Test";
"でぃてぃーる" = "Detail";
.
.
.
"フッター" = "Footer";

2 ViewController にローカライズ処理を書く

// FIXME: Xcodeのバグがなおったら消したい
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = super.tableView(tableView, cellForRowAt: indexPath)
    if let text = cell.textLabel?.text {
      cell.textLabel?.text = tmpLocalizedString(text)
    }
    if let text =  cell.detailTextLabel?.text {
      cell.detailTextLabel?.text = tmpLocalizedString(text)
    }
    return cell
}

private func tmpLocalizedString(_ key: String) -> String {
  return NSLocalizedString(key, tableName: "TMPLocalizable", comment: "")
}

これで表示は想定通りになります。

さいごに

上記のような対応で無事ローカライズ対応することができました!!!が、対応後すぐにバグが修正されたXcode11.2がリリースされました...

私はそっと revert しました:see_no_evil:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Cell上のUIImageViewでAPIから取得した画像(URL)を表示させたこと

概要

Cell上のUIImageViewにAPIから取得した画像(URL)を表示するということが結構難しかったのでメモします。
※URLSessionを使用します。

作業の順序

1.APIから取得したURLを使ってサーバーから画像を取得
2.imageviewに取得した画像をセットする

実装

SongTableViewCell.swift

class SongTableViewCell: UITableViewCell {

    @IBOutlet weak var trackImage: UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

    }

//"Result", "artworkUrl100"はAPI実装のファイルから取得している値
    func setup(set: Result) {
//2:imageviewに取得した画像をセットする
        trackImage.setImageByDefault(with: set.artworkUrl100)

    }
}

//1:APIから取得したURLを使ってサーバーから画像を取得
extension UIImageView {

    func setImageByDefault(with url: URL) {
        URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            if error == nil, case .some(let result) = data, let image = UIImage(data: result) {
               //ここでメインスレッド実行するように記述(無いとサブスレッドで実行されて落ちる)
                DispatchQueue.main.async {
                    self?.image = image
                }

            } else {
                // error handling

            }
        }.resume()
    }

}

ListTableViewController.swift
 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "SongTableViewCell") as? SongTableViewCell else {
            return SongTableViewCell()
        }
        let info = resultsCount?.results?[indexPath.row] else {
             return SongTableViewCell()
        }
        cell.setup(set: info)
        return cell
    }

ひとまず画像は取得できるようになりました。

終わりに

以下の記事などを参考にして実装しました。

https://qiita.com/H_Crane/items/422811dfc18ae919f8a4
https://qiita.com/nbapps_dev/items/d22837b04d8bb0a3127d

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HealthKitで水泳の各ラップのストローク数が取りたい

はじめに

Apple Watchで水泳のワークアウトが測定できるの知ってました?
IMG_4193.PNG

ワークアウトアプリにプールスイミングがあるので、これを実行しながらプールで泳げば、測定できます。
測定結果はApple WatchとペアリングしているiPhoneのアクティビティアプリで確認できます。
サンプルデータですが、このように表示されます。
IMG_4195.jpg

ラップ数や泳いでいるときの泳法も測定できていて、すごい! てなるのですが、
このアプリでは各ラップのストローク数は確認できません。
HealthKitで取れるだろう、と思ってやってみました。

アプリでHealthKitが使えるようにする

公式ドキュメントを参照して、手順通りに行います。
プロジェクトファイルとInfo.plistを編集します。

水泳のワークアウトデータを取ってみる

サンプルコード

import HealthKit

let healthStore = HKHealthStore()

let readDataTypes: Set<HKObjectType> = [HKWorkoutType.workoutType()]

healthStore.requestAuthorization(
    toShare: nil,
    read: readDataTypes,
    completion: {(success, error) in
        if success {
            let predicate = HKQuery.predicateForWorkouts(with: HKWorkoutActivityType.swimming)
            let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
            let sampleQuery = HKSampleQuery(
                sampleType: HKObjectType.workoutType(),
                predicate: predicate,
                limit: HKObjectQueryNoLimit,
                sortDescriptors: [sortDescriptor],
                resultsHandler: {(_, results, error) in
                    if error == nil {
                        let workouts = results as! [HKWorkout]
                        if let workout = workouts.first {
                            print(workout.workoutEvents)
                        }
                    }
            })

            healthStore.execute(sampleQuery)
        }
})

このコードをざっと説明すると、
1. ヘルスケアデータからワークアウトを取る許可を取得する
2. swimmingのワークアウトを最新から順に取得する
3. 最新のワークアウトのworkoutEventsを出力する
になります。
print文で出力したworkoutEventsから、サンプルとして一つを抜き出してみると

HKWorkoutEventTypeLap, <_NSConcreteDateInterval: 0x28300eb40> (Start Date) 2019-10-24 10:56:05 +0000 + (Duration) 6.520985 seconds = (End Date) 2019-10-24 10:56:11 +0000 {
    HKSwimmingStrokeStyle = 5;

水泳のワークアウトの1ラップのデータと読み取れますが、ストローク数と思われるデータはありません。
公式のドキュメントを参照すると分かるのですが、HKWorkoutEventクラスにはストローク数と思われるものはありません。
また、HKWorkoutクラスにtotalSwimmingStrokeCountというものはあるのですが、これはワークアウト単位のものであって、各ラップのストローク数ではありません。
別のアプローチが必要です。

そこで、ヘルスケアデータには水泳のストローク数というタイプがあるので、そこから取得しました。

水泳のストローク数から取得する

let readDataTypes: Set<HKObjectType> = [HKWorkoutType.workoutType(),
                                        HKObjectType.quantityType(forIdentifier: .swimmingStrokeCount)!]

に書き換えて、先ほど取得したworkoutEventsを使って

let type = HKObjectType.quantityType(forIdentifier: .swimmingStrokeCount)!

for (index, workoutEvent) in workoutEvents.enumerated()  {
    // workoutEventのtypeにはlap, segment, pauseなどがあります
    if workoutEvent.type == .lap {
        let predicate = HKQuery.predicateForSamples(withStart: workoutEvent.dateInterval.start, end: workoutEvent.dateInterval.end, options: .strictStartDate)
        let query = HKStatisticsQuery(quantityType: type,
                                      quantitySamplePredicate: predicate,
                                      options: [.cumulativeSum]) { (_, statistic, error) in
                                          guard let statistic = statistic, error == nil else {
                                              return
                                          }
                                          let sum = statistic.sumQuantity()?.doubleValue(for: HKUnit.count()) ?? 0
                                          print("stroke:\(sum) index:\(index)")
        }

        healthStore.execute(query)
    }
}

print文の箇所は非同期に実行されるため、インデックスも追加して、workoutEventsの何番目か分かるようにしました。

stroke:8.0 index:1
stroke:3.0 index:2
stroke:15.0 index:3
stroke:11.0 index:4
stroke:11.0 index:5
stroke:6.0 index:6
stroke:11.0 index:7
stroke:10.0 index:9
stroke:7.0 index:10
stroke:13.0 index:12
stroke:7.0 index:15
stroke:10.0 index:13
stroke:4.0 index:16
stroke:11.0 index:14

出力はworkoutEventsの順番通りではありませんが、これでストローク数を取得できました。

HKStatisticsQueryの公式ドキュメント以外に参考になる事例を見つけられなかったので、
より良いやり方や、同じようなことを試みたことがあれば、コメント下さい。

サンプルコードはこちらにも上げました。

ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スクロールビュー

func scroll(){
        //スクロール処理
        scrollView = UIScrollView(frame: CGRect(origin: .zero, size: view.bounds.size))
        imageView = UIImageView(image: UIImage(named: name))
        scrollView.contentSize = imageView.bounds.size
        scrollView.addSubview(imageView)
        view.addSubview(scrollView)
    }
    func zoom(){
        //ズーム機能
        scrollView.minimumZoomScale = 0.005
        scrollView.maximumZoomScale = 50
        scrollView.delegate = self
    }


    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift でテスト駆動開発をやってみた

弊社弊チームでは TDD を習得するために Kent Beck(著), 和田 卓人(翻訳)の テスト駆動開発 の輪読をしています。テスト駆動開発 はとても勉強になりました。
「写経ではなく別の言語で挑戦することで、TDDをより深く習得したい」 & 「Swift自体の勉強がしたい」と思い、Swift でテスト駆動開発(TDD)に挑戦してみました。
あと会社の先輩が Elm でTDD をしていたので真似してみました。
プロジェクトファイルはこちらに置いてあります。O-Junpei/TDD-Swift

方針と注意事項

本文の内容やコードは、著作権を考慮して極力載せず、Swift のコードとテストのTODOリストだけでやっていきたいと思っています。Java のコードと似せるため型は明示的に書きました。
もし関係者の方々から注意喚起があれば記事をすぐに削除します。言語が異なるので進め方が多少異なる場合があります。

事前準備

Include Unit Test にチェックを入れ、テスト環境が整ったプロジェクトを作成します。

スクリーンショット 2019-11-13 11.22.02.png

第1章 仮実装

米ドルやフランを扱うことのできる多国通貨オプジェクトを作成します。
第1章では米ドルの掛け算を実装していきます。
あまり詰まることがなく、Java の実装を参考に Swift を書きことができました。

TODOリスト

  • $5 + 10CHF = $10 (レートが2:1の場合)
  • $5 * 2 = $10
Dollar.swift
import Foundation

class Dollar {
    var amount: Int

    init(amount: Int) {
        self.amount = amount
    }

    func times(multiplier: Int) {
        amount *= multiplier
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {

    func testMultiplication() {
        let five: Dollar = Dollar(amount: 5)
        five.times(multiplier: 2)
        XCTAssertEqual(10, five.amount)
    }
}

第2章 明白な実装

今章は詰まることがなくSwiftで実装することができました。

TODOリスト

  • $5 + 10CHF = $10 (レートが2:1の場合)
  • $5 * 2= $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
Dollar.swift
import Foundation

class Dollar {
    var amount: Int

    init(amount: Int) {
        self.amount = amount
    }

    func times(multiplier: Int) -> Dollar {
        return Dollar(amount: amount * multiplier)
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {

    func testMultiplication() {
        let five: Dollar = Dollar(amount: 5)
        var product: Dollar = five.times(multiplier: 2)
        XCTAssertEqual(10, product.amount)
        product = five.times(multiplier: 3)
        XCTAssertEqual(15, product.amount)
    }
}

第3章 三角測量

Java の実装では equals メソッドで$ドル同士を比較しています。(Java ではプリミティブ型の比較に ==演算子、参照型の比較に equalsメソッドを使用する。)
Swift の実装では ==演算子を使用して比較しました。
Equatable プロトコルに準拠することで ==演算子を使用することができます。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 * 2 = $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
  • equals()
Dollar.swift
import Foundation

class Dollar: Equatable {
    var amount: Int

    init(amount: Int) {
        self.amount = amount
    }

    func times(multiplier: Int) -> Dollar {
        return Dollar(amount: amount * multiplier)
    }

    static func == (lhs: Dollar, rhs: Dollar) -> Bool {
        lhs.amount == rhs.amount
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {

    func testMultiplication() {
        let five: Dollar = Dollar(amount: 5)
        var product: Dollar = five.times(multiplier: 2)
        XCTAssertEqual(10, product.amount)
        product = five.times(multiplier: 3)
        XCTAssertEqual(15, product.amount)
    }

    func testEquality() {
        XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5))
        XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6))
    }
}

第4章 意図を語るテスト

今章は詰まることがなくSwiftで実装することができました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 * 2 = $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
  • equals()
  • hashCode()
  • nullとの等価性比較
  • 他のオブジェクトとの等価性比較
Dollar.swift
import Foundation

class Dollar: Equatable {
    var amount: Int

    init(amount: Int) {
        self.amount = amount
    }

    func times(multiplier: Int) -> Dollar {
        return Dollar(amount: amount * multiplier)
    }

    static func == (lhs: Dollar, rhs: Dollar) -> Bool {
        lhs.amount == rhs.amount
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {

    func testMultiplication() {
        let five: Dollar = Dollar(amount: 5)
        XCTAssertEqual(Dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Dollar(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5))
        XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6))
    }
}

第5章 原則をあえて破るとき

今章は詰まることがなくSwiftで実装することができました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 * 2 = $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
  • equals()
  • hashCode()
  • nullとの等価性比較
  • 他のオブジェクトとの等価性比較
  • 5 CHF * 2 = 10 CHF
Dollar.swift
import Foundation

class Dollar: Equatable {
    var amount: Int

    init(amount: Int) {
        self.amount = amount
    }

    func times(multiplier: Int) -> Dollar {
        return Dollar(amount: amount * multiplier)
    }

    static func == (lhs: Dollar, rhs: Dollar) -> Bool {
        lhs.amount == rhs.amount
    }
}
Franc.swift
import Foundation

class Franc: Equatable {
    var amount: Int

    init(amount: Int) {
        self.amount = amount
    }

    func times(multiplier: Int) -> Franc {
        return Franc(amount: amount * multiplier)
    }

    static func == (lhs: Franc, rhs: Franc) -> Bool {
        lhs.amount == rhs.amount
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {

    func testMultiplication() {
        let five: Dollar = Dollar(amount: 5)
        XCTAssertEqual(Dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Dollar(amount: 15), five.times(multiplier: 3))
    }

    func testFrancMultiplication() {
        let five: Franc = Franc(amount: 5)
        XCTAssertEqual(Franc(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Franc(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5))
        XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6))
    }
}

第6章 テスト不足に気づいたら

今章は詰まることがなくSwiftで実装することができました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 * 2 = $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
  • equals()
  • hashCode()
  • nullとの等価性比較
  • 他のオブジェクトとの等価性比較
  • 5 CHF * 2 = 10 CHF
  • DollarとFrancの重複
  • equalsの一般化
  • timesの一般化
Money.swift
class Money: Equatable {
    let amount: Int

    init(amount: Int) {
        self.amount = amount
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        lhs.amount == rhs.amount
    }
}
Dollar.swift
class Dollar: Money {
    override init(amount: Int) {
        super.init(amount: amount)
    }

    func times(multiplier: Int) -> Dollar {
        return Dollar(amount: amount * multiplier)
    }
}
Franc.swift
class Franc: Money {

    override init(amount: Int) {
        super.init(amount: amount)
    }

    func times(multiplier: Int) -> Franc {
        return Franc(amount: amount * multiplier)
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {

    func testMultiplication() {
        let five: Dollar = Dollar(amount: 5)
        XCTAssertEqual(Dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Dollar(amount: 15), five.times(multiplier: 3))
    }

    func testFrancMultiplication() {
        let five: Franc = Franc(amount: 5)
        XCTAssertEqual(Franc(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Franc(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5))
        XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6))
        XCTAssertTrue(Franc(amount: 5) == Franc(amount: 5))
        XCTAssertFalse(Franc(amount: 5) == Franc(amount: 6))
    }
}

第7章 疑念をテストに翻訳する

7章ではフランが登場しました。
Java の実装では instanceOf メソッドで型の比較をしています。
Swift の実装では type(of: XXX) メソッドを使用することで比較しました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 * 2 = $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
  • equals()
  • hashCode()
  • nullとの等価性比較
  • 他のオブジェクトとの等価性比較
  • 5 CHF * 2 = 10 CHF
  • DollarとFrancの重複
  • equalsの一般化
  • timesの一般化
  • FrancとDollarを比較する
Money.swift
class Money: Equatable {
    let amount: Int

    init(amount: Int) {
        self.amount = amount
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && String(describing: type(of: lhs)) == String(describing: type(of: rhs))
    }
}
Dollar.swift
class Dollar: Money {
    override init(amount: Int) {
        super.init(amount: amount)
    }

    func times(multiplier: Int) -> Dollar {
        return Dollar(amount: amount * multiplier)
    }
}
Franc.swift
class Franc: Money {

    override init(amount: Int) {
        super.init(amount: amount)
    }

    func times(multiplier: Int) -> Franc {
        return Franc(amount: amount * multiplier)
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {

    func testMultiplication() {
        let five: Dollar = Dollar(amount: 5)
        XCTAssertEqual(Dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Dollar(amount: 15), five.times(multiplier: 3))
    }

    func testFrancMultiplication() {
        let five: Franc = Franc(amount: 5)
        XCTAssertEqual(Franc(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Franc(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Dollar(amount: 5) == Dollar(amount: 5))
        XCTAssertFalse(Dollar(amount: 5) == Dollar(amount: 6))
        XCTAssertTrue(Franc(amount: 5) == Franc(amount: 5))
        XCTAssertFalse(Franc(amount: 5) == Franc(amount: 6))
        XCTAssertFalse(Franc(amount: 5) == Dollar(amount: 5))
    }
}

第8章 実装を隠す

今章は少し困りました。
Java の実装で Abstract クラスが登場したためです。
Swift には Abstract クラスに相当するものが無いため、 Money クラスを具象クラスとして定義してしまいました。
もしもっと良い書き方があればコメントいただきたいです。。。!

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 * 2 = $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
  • equals()
  • hashCode()
  • nullとの等価性比較
  • 他のオブジェクトとの等価性比較
  • 5 CHF * 2 = 10 CHF
  • DollarとFrancの重複
  • equalsの一般化
  • timesの一般化
  • FrancとDollarを比較する
  • 通過の概念
Money.swift
class Money: Equatable {
    let amount: Int

    init(amount: Int) {
        self.amount = amount
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && String(describing: type(of: lhs)) == String(describing: type(of: rhs))
    }

    static func dollar(amount: Int) -> Money {
        return Dollar(amount: amount)
    }

    static func franc(amount: Int) -> Money {
        return Franc(amount: amount)
    }

    func times(multiplier: Int) -> Money {
        fatalError()
    }
}
Dollar.swift
class Dollar: Money {
    override init(amount: Int) {
        super.init(amount: amount)
    }

    override func times(multiplier: Int) -> Money {
        return Dollar(amount: amount * multiplier)
    }
}
Franc.swift
class Franc: Money {

    override init(amount: Int) {
        super.init(amount: amount)
    }

    override func times(multiplier: Int) -> Money {
        return Franc(amount: amount * multiplier)
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {

    func testMultiplication() {
        let five: Money = Money.dollar(amount: 5)
        XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3))
    }

    func testFrancMultiplication() {
        let five: Money = Money.franc(amount: 5)
        XCTAssertEqual(Money.franc(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.franc(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5))
        XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6))
        XCTAssertTrue(Money.franc(amount: 5) == Money.franc(amount: 5))
        XCTAssertFalse(Money.franc(amount: 5) == Money.franc(amount: 6))
        XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5))
    }
}

第9章 歩幅の調整

今章は詰まることがなくSwiftで実装することができました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 * 2 = $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
  • equals()
  • hashCode()
  • nullとの等価性比較
  • 他のオブジェクトとの等価性比較
  • 5 CHF * 2 = 10 CHF
  • DollarとFrancの重複
  • equalsの一般化
  • timesの一般化
  • FrancとDollarを比較する
  • 通過の概念
  • testFrancMultiplicationを削除する
Money.swift
class Money: Equatable {
    let amount: Int
    let currency: String

    init(amount: Int, currency: String) {
        self.amount = amount
        self.currency = currency
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && String(describing: type(of: lhs)) == String(describing: type(of: rhs))
    }

    static func dollar(amount: Int) -> Money {
        return Dollar(amount: amount, currency: "USD")
    }

    static func franc(amount: Int) -> Money {
        return Franc(amount: amount, currency: "CHF")
    }

    func times(multiplier: Int) -> Money {
        fatalError("Must be overridden")
    }
}
Dollar.swift
class Dollar: Money {
    override init(amount: Int, currency: String) {
        super.init(amount: amount, currency: currency)
    }

    override func times(multiplier: Int) -> Money {
        return Money.dollar(amount: amount * multiplier)
    }
}
Franc.swift
class Franc: Money {
    override init(amount: Int, currency: String) {
        super.init(amount: amount, currency: currency)
    }

    override func times(multiplier: Int) -> Money {
        return Money.franc(amount: amount * multiplier)
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {
    func testMultiplication() {
        let five: Money = Money.dollar(amount: 5)
        XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3))
    }

    func testFrancMultiplication() {
        let five: Money = Money.franc(amount: 5)
        XCTAssertEqual(Money.franc(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.franc(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5))
        XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6))
        XCTAssertTrue(Money.franc(amount: 5) == Money.franc(amount: 5))
        XCTAssertFalse(Money.franc(amount: 5) == Money.franc(amount: 6))
        XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5))
    }

    func testCurrency() {
        XCTAssertEqual("USD", Money.dollar(amount: 1).currency)
        XCTAssertEqual("CHF", Money.franc(amount: 1).currency)
    }
}

第10章 テストに聞いてみる

エラーメッセージをわかりやすくするため、Java の実装では toString メソッドを作成しました。
Swift で toString メソッドに相当するものは description Computed property であるため、CustomStringConvertible プロトコルに準拠して実装しました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 * 2 = $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
  • equals()
  • hashCode()
  • nullとの等価性比較
  • 他のオブジェクトとの等価性比較
  • 5 CHF * 2 = 10 CHF
  • DollarとFrancの重複
  • equalsの一般化
  • timesの一般化
  • FrancとDollarを比較する
  • 通過の概念
  • testFrancMultiplicationを削除する
Money.swift
class Money: Equatable, CustomStringConvertible {
    let amount: Int
    let currency: String

    init(amount: Int, currency: String) {
        self.amount = amount
        self.currency = currency
    }

    var description: String {
        return "\(amount) \(currency.description))"
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && lhs.currency == rhs.currency
    }

    static func dollar(amount: Int) -> Money {
        return Dollar(amount: amount, currency: "USD")
    }

    static func franc(amount: Int) -> Money {
        return Franc(amount: amount, currency: "CHF")
    }

    func times(multiplier: Int) -> Money {
        return Money(amount: amount * multiplier, currency: currency)
    }
}
Dollar.swift
class Dollar: Money {
    override init(amount: Int, currency: String) {
        super.init(amount: amount, currency: currency)
    }
}
Franc.swift
class Franc: Money {
    override init(amount: Int, currency: String) {
        super.init(amount: amount, currency: currency)
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {
    func testMultiplication() {
        let five: Money = Money.dollar(amount: 5)
        XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3))
    }

    func testFrancMultiplication() {
        let five: Money = Money.franc(amount: 5)
        XCTAssertEqual(Money.franc(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.franc(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5))
        XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6))
        XCTAssertTrue(Money.franc(amount: 5) == Money.franc(amount: 5))
        XCTAssertFalse(Money.franc(amount: 5) == Money.franc(amount: 6))
        XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5))
    }

    func testCurrency() {
        XCTAssertEqual("USD", Money.dollar(amount: 1).currency)
        XCTAssertEqual("CHF", Money.franc(amount: 1).currency)
    }

    func testDifferentClassEquality() {
        XCTAssertTrue(Money(amount: 10, currency: "CHF") == Franc(amount: 10, currency: "CHF"))
    }
}

第11章 不要になったら消す

今章は詰まることがなくSwiftで実装することができました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 * 2 = $10
  • amountをprivateにする
  • Dollarの副作用どうする?
  • Moneyの丸め処理どうする?
  • equals()
  • hashCode()
  • nullとの等価性比較
  • 他のオブジェクトとの等価性比較
  • 5 CHF * 2 = 10 CHF
  • DollarとFrancの重複
  • equalsの一般化
  • timesの一般化
  • FrancとDollarを比較する
  • 通過の概念
  • testFrancMultiplicationを削除する
Money.swift
class Money: Equatable, CustomStringConvertible {
    let amount: Int
    let currency: String

    init(amount: Int, currency: String) {
        self.amount = amount
        self.currency = currency
    }

    var description: String {
        return "\(amount) \(currency.description))"
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && lhs.currency == rhs.currency
    }

    static func dollar(amount: Int) -> Money {
        return Money(amount: amount, currency: "USD")
    }

    static func franc(amount: Int) -> Money {
        return Money(amount: amount, currency: "CHF")
    }

    func times(multiplier: Int) -> Money {
        return Money(amount: amount * multiplier, currency: currency)
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {
    func testMultiplication() {
        let five: Money = Money.dollar(amount: 5)
        XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5))
        XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6))
        XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5))
    }

    func testCurrency() {
        XCTAssertEqual("USD", Money.dollar(amount: 1).currency)
        XCTAssertEqual("CHF", Money.franc(amount: 1).currency)
    }
}

第12章 設計とメタファー

今章は詰まることがなくSwiftで実装することができました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 + $5 = $10
Money.swift
class Money: Equatable, CustomStringConvertible, Expression {
    let amount: Int
    let currency: String

    init(amount: Int, currency: String) {
        self.amount = amount
        self.currency = currency
    }

    var description: String {
        return "\(amount) \(currency.description))"
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && lhs.currency == rhs.currency
    }

    static func dollar(amount: Int) -> Money {
        return Money(amount: amount, currency: "USD")
    }

    static func franc(amount: Int) -> Money {
        return Money(amount: amount, currency: "CHF")
    }

    func times(multiplier: Int) -> Money {
        return Money(amount: amount * multiplier, currency: currency)
    }

    func plus(addend: Money) -> Expression {
        return Money(amount: amount + addend.amount, currency: currency)
    }
}
Expression.swift
protocol Expression {
}
Bank.swift
class Bank {
    func reduce(source: Expression, to: String) -> Money {
        return Money.dollar(amount: 10)
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {
    func testMultiplication() {
        let five: Money = Money.dollar(amount: 5)
        XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5))
        XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6))
        XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5))
    }

    func testCurrency() {
        XCTAssertEqual("USD", Money.dollar(amount: 1).currency)
        XCTAssertEqual("CHF", Money.franc(amount: 1).currency)
    }
}

第13章 実装を導くテスト

Java の実装の途中で型のキャストを行う箇所があります。
if let 構文で安全にキャストすることができます。

if let money = source as? Money {
    return money
}

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 + $5 = $10
Money.swift
class Money: Equatable, CustomStringConvertible, Expression {
    let amount: Int
    let currency: String

    init(amount: Int, currency: String) {
        self.amount = amount
        self.currency = currency
    }

    var description: String {
        return "\(amount) \(currency.description))"
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && lhs.currency == rhs.currency
    }

    static func dollar(amount: Int) -> Money {
        return Money(amount: amount, currency: "USD")
    }

    static func franc(amount: Int) -> Money {
        return Money(amount: amount, currency: "CHF")
    }

    func times(multiplier: Int) -> Money {
        return Money(amount: amount * multiplier, currency: currency)
    }

    func plus(addend: Money) -> Expression {
        return Sum(augend: self, addend: addend)
    }

    func reduce(to: String)-> Money {
        return self
    }
}
Sum.swift
class Sum: Expression {
    let augend: Money
    let addend: Money

    init(augend: Money, addend: Money) {
        self.augend = augend
        self.addend = addend
    }

    func reduce(to: String) -> Money {
        let amount: Int = augend.amount + addend.amount
        return Money(amount: amount, currency: to)
    }
}
Expression.swift
protocol Expression {
    func reduce(to: String) -> Money
}
Bank.swift
class Bank {
    func reduce(source: Expression, to: String) -> Money {
        return source.reduce(to: to)
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {
    func testMultiplication() {
        let five: Money = Money.dollar(amount: 5)
        XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5))
        XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6))
        XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5))
    }

    func testCurrency() {
        XCTAssertEqual("USD", Money.dollar(amount: 1).currency)
        XCTAssertEqual("CHF", Money.franc(amount: 1).currency)
    }

    func testSimpleAddition() {
        let five: Money = Money.dollar(amount: 5)
        let sum: Expression = five.plus(addend: five)
        let bank: Bank = Bank()
        let reduced: Money = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 10), reduced)
    }

    func testPlusReturnSum() {
        let five: Money = Money.dollar(amount: 5)
        let result: Expression = five.plus(addend: five)
        let sum: Sum = result as! Sum
        XCTAssertEqual(five, sum.addend)
    }

    func testResuceSum() {
        let sum: Expression = Sum(augend: Money.dollar(amount: 3), addend: Money.dollar(amount: 4))
        let bank: Bank = Bank()
        let result: Money = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 7), result)
    }

    func testReduceMoney() {
        let bank: Bank = Bank()
        let result: Money = bank.reduce(source: Money.dollar(amount: 1), to: "USD")
        XCTAssertEqual(Money.dollar(amount: 1), result)
    }
}

第14章 学習用テストと回帰テスト

今章は詰まることがなくSwiftで実装することができました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 + $5 = $10
  • $5 + $5 が Money を返す
  • Bank.reduce(Money)
  • Money を変換して換算を行う
  • Reduce(Bank, String)
Money.swift
class Money: Equatable, CustomStringConvertible, Expression {
    let amount: Int
    let currency: String

    init(amount: Int, currency: String) {
        self.amount = amount
        self.currency = currency
    }

    var description: String {
        return "\(amount) \(currency.description))"
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && lhs.currency == rhs.currency
    }

    static func dollar(amount: Int) -> Money {
        return Money(amount: amount, currency: "USD")
    }

    static func franc(amount: Int) -> Money {
        return Money(amount: amount, currency: "CHF")
    }

    func times(multiplier: Int) -> Money {
        return Money(amount: amount * multiplier, currency: currency)
    }

    func plus(addend: Money) -> Expression {
        return Sum(augend: self, addend: addend)
    }

    func reduce(bank: Bank, to: String)-> Money {
        let rate: Int = bank.rate(from: currency, to: to)
        return Money(amount: amount / rate, currency: to)
    }
}
Sum.swift
class Sum: Expression {
    let augend: Money
    let addend: Money

    init(augend: Money, addend: Money) {
        self.augend = augend
        self.addend = addend
    }

    func reduce(bank:Bank, to: String) -> Money {
        let amount: Int = augend.amount + addend.amount
        return Money(amount: amount, currency: to)
    }
}
Expression.swift
protocol Expression {
    func reduce(bank: Bank, to: String) -> Money
}
Bank.swift
class Bank {
    private var rates: [Pair: Int] = [:]

    func reduce(source: Expression, to: String) -> Money {
        return source.reduce(bank: self, to: to)
    }

    func addRate(from: String, to: String, rate: Int) {
        rates[Pair(from: from, to: to)] = rate
    }

    func rate(from: String, to: String) -> Int {
        if from == to {
            return 1
        }

        guard let rate = rates[Pair(from: from, to: to)] else {
            fatalError("未対応の通過です")
        }
        return rate
    }
}
Pair.swift
class Pair: Hashable {
    private let from: String
    private let to: String

    init(from: String, to: String) {
        self.from = from
        self.to = to
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(from)
        hasher.combine(to)
    }

    static func == (lhs: Pair, rhs: Pair) -> Bool {
        return lhs.from == rhs.from && lhs.to == rhs.to
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {
    func testMultiplication() {
        let five: Money = Money.dollar(amount: 5)
        XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3))
    }

    func testEquality() {
        XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5))
        XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6))
        XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5))
    }

    func testCurrency() {
        XCTAssertEqual("USD", Money.dollar(amount: 1).currency)
        XCTAssertEqual("CHF", Money.franc(amount: 1).currency)
    }

    func testSimpleAddition() {
        let five: Money = Money.dollar(amount: 5)
        let sum: Expression = five.plus(addend: five)
        let bank: Bank = Bank()
        let reduced: Money = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 10), reduced)
    }

    func testPlusReturnSum() {
        let five: Money = Money.dollar(amount: 5)
        let result: Expression = five.plus(addend: five)
        let sum: Sum = result as! Sum
        XCTAssertEqual(five, sum.addend)
    }

    func testResuceSum() {
        let sum: Expression = Sum(augend: Money.dollar(amount: 3), addend: Money.dollar(amount: 4))
        let bank: Bank = Bank()
        let result: Money = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 7), result)
    }

    func testReduceMoney() {
        let bank: Bank = Bank()
        let result: Money = bank.reduce(source: Money.dollar(amount: 1), to: "USD")
        XCTAssertEqual(Money.dollar(amount: 1), result)
    }

    func testReduceMoneyDifferentCurrency() {
        let bank: Bank = Bank()
        bank.addRate(from: "CHF", to: "USD", rate: 2)
        let result: Money = bank.reduce(source: Money.franc(amount: 2), to: "USD")
        XCTAssertEqual(Money.dollar(amount: 1), result)
    }

    func testIdentityRate() {
        XCTAssertEqual(1, Bank().rate(from: "USD", to: "USD"))
    }
}

第15章 テスト任せとコンパイラ任せ

Java の実装を参考にしながら Swift で実装したところ、 Protocol type 'Expression' cannot conform to 'Equatable' because only concrete types can conform to protocols と怒られてしまいました。
Swift だと protcol 同士の比較ができなそうなので、Money クラスで比較しました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 + $5 = $10
  • $5 + $5 = $10がMoneyを返す
  • Bank.reduce(Money)
  • Moneyを変換して換算を行う
  • Reduce(Bank, String)
Money.swift
class Money: Equatable, CustomStringConvertible, Expression {
    let amount: Int
    let currency: String

    init(amount: Int, currency: String) {
        self.amount = amount
        self.currency = currency
    }

    var description: String {
        return "\(amount) \(currency.description))"
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && lhs.currency == rhs.currency
    }

    static func dollar(amount: Int) -> Money {
        return Money(amount: amount, currency: "USD")
    }

    static func franc(amount: Int) -> Money {
        return Money(amount: amount, currency: "CHF")
    }

    func times(multiplier: Int) -> Expression {
        return Money(amount: amount * multiplier, currency: currency)
    }

    func plus(addend: Expression) -> Expression {
        return Sum(augend: self, addend: addend)
    }

    func reduce(bank: Bank, to: String)-> Money {
        let rate: Int = bank.rate(from: currency, to: to)
        return Money(amount: amount / rate, currency: to)
    }
}
Sum.swift
class Sum: Expression {
    let augend: Expression
    let addend: Expression

    init(augend: Expression, addend: Expression) {
        self.augend = augend
        self.addend = addend
    }

    func plus(addend: Expression) -> Expression {
        fatalError()
    }

    func reduce(bank: Bank, to: String) -> Money {
        let amount: Int = augend.reduce(bank: bank, to: to).amount + addend.reduce(bank: bank, to: to).amount
        return Money(amount: amount, currency: to)
    }
}
Expression.swift
protocol Expression {
    func plus(addend: Expression) -> Expression
    func reduce(bank: Bank, to: String) -> Money
}
Bank.swift
class Bank {
    private var rates: [Pair: Int] = [:]

    func reduce(source: Expression, to: String) -> Money {
        return source.reduce(bank: self, to: to)
    }

    func addRate(from: String, to: String, rate: Int) {
        rates[Pair(from: from, to: to)] = rate
    }

    func rate(from: String, to: String) -> Int {
        if from == to {
            return 1
        }

        guard let rate = rates[Pair(from: from, to: to)] else {
            fatalError("未対応の通過です")
        }
        return rate
    }
}
Pair.swift
class Pair: Hashable {
    private let from: String
    private let to: String

    init(from: String, to: String) {
        self.from = from
        self.to = to
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(from)
        hasher.combine(to)
    }

    static func == (lhs: Pair, rhs: Pair) -> Bool {
        return lhs.from == rhs.from && lhs.to == rhs.to
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {
    func testMultiplication() {
        let five: Money = Money.dollar(amount: 5)
        // XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2).reduce(bank: Bank(), to: "USD"))
        // XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3))
        XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3).reduce(bank: Bank(), to: "USD"))
    }

    func testEquality() {
        XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5))
        XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6))
        XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5))
    }

    func testCurrency() {
        XCTAssertEqual("USD", Money.dollar(amount: 1).currency)
        XCTAssertEqual("CHF", Money.franc(amount: 1).currency)
    }

    func testSimpleAddition() {
        let five: Money = Money.dollar(amount: 5)
        let sum: Expression = five.plus(addend: five)
        let bank: Bank = Bank()
        let reduced: Money = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 10), reduced)
    }

    func testPlusReturnSum() {
        let five: Money = Money.dollar(amount: 5)
        let result: Expression = five.plus(addend: five)
        let sum: Sum = result as! Sum
        // XCTAssertEqual(five, sum.added)
        XCTAssertEqual(five, sum.addend.reduce(bank: Bank(), to: "USD"))
    }

    func testResuceSum() {
        let sum: Expression = Sum(augend: Money.dollar(amount: 3), addend: Money.dollar(amount: 4))
        let bank: Bank = Bank()
        let result: Money = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 7), result)
    }

    func testReduceMoney() {
        let bank: Bank = Bank()
        let result: Money = bank.reduce(source: Money.dollar(amount: 1), to: "USD")
        XCTAssertEqual(Money.dollar(amount: 1), result)
    }

    func testReduceMoneyDifferentCurrency() {
        let bank: Bank = Bank()
        bank.addRate(from: "CHF", to: "USD", rate: 2)
        let result: Money = bank.reduce(source: Money.franc(amount: 2), to: "USD")
        XCTAssertEqual(Money.dollar(amount: 1), result)
    }

    func testIdentityRate() {
        XCTAssertEqual(1, Bank().rate(from: "USD", to: "USD"))
    }

    func testMixedAddition() {
        let fiveBucks: Expression = Money.dollar(amount: 5)
        let tenFrancs: Expression = Money.franc(amount: 10)
        let bank: Bank = Bank()
        bank.addRate(from: "CHF", to: "USD", rate: 2)
        let result: Money = bank.reduce(source: fiveBucks.plus(addend: tenFrancs), to: "USD")
        XCTAssertEqual(Money.dollar(amount: 10), result)
    }
}

第16章 将来の読み手を考えたテスト

今章は詰まることがなくSwiftで実装することができました。

TODOリスト

  • $5 + 10CHF = $10(レートが2:1の場合)
  • $5 + $5 = $10
  • $5 + $5 = $10がMoneyを返す
  • Bank.reduce(Money)
  • Moneyを変換して換算を行う
  • Reduce(Bank, String)
  • Sum.plus
  • Expression.times
Money.swift
class Money: Equatable, CustomStringConvertible, Expression {
    let amount: Int
    let currency: String

    init(amount: Int, currency: String) {
        self.amount = amount
        self.currency = currency
    }

    var description: String {
        return "\(amount) \(currency.description))"
    }

    static func == (lhs: Money, rhs: Money) -> Bool {
        return lhs.amount == rhs.amount && lhs.currency == rhs.currency
    }

    static func dollar(amount: Int) -> Money {
        return Money(amount: amount, currency: "USD")
    }

    static func franc(amount: Int) -> Money {
        return Money(amount: amount, currency: "CHF")
    }

    func times(multiplier: Int) -> Expression {
        return Money(amount: amount * multiplier, currency: currency)
    }

    func plus(addend: Expression) -> Expression {
        return Sum(augend: self, addend: addend)
    }

    func reduce(bank: Bank, to: String)-> Money {
        let rate: Int = bank.rate(from: currency, to: to)
        return Money(amount: amount / rate, currency: to)
    }
}
Sum.swift
class Sum: Expression {
    let augend: Expression
    let addend: Expression

    init(augend: Expression, addend: Expression) {
        self.augend = augend
        self.addend = addend
    }

    func times(multiplier: Int) -> Expression {
        return Sum(augend: augend.times(multiplier: multiplier), addend: addend.times(multiplier: multiplier))
    }

    func plus(addend: Expression) -> Expression {
        return Sum(augend: self, addend: addend)
    }

    func reduce(bank: Bank, to: String) -> Money {
        let amount: Int = augend.reduce(bank: bank, to: to).amount + addend.reduce(bank: bank, to: to).amount
        return Money(amount: amount, currency: to)
    }
}
Expression.swift
protocol Expression {
    func times(multiplier: Int) -> Expression
    func plus(addend: Expression) -> Expression
    func reduce(bank: Bank, to: String) -> Money
}
Bank.swift
class Bank {
    private var rates: [Pair: Int] = [:]

    func reduce(source: Expression, to: String) -> Money {
        return source.reduce(bank: self, to: to)
    }

    func addRate(from: String, to: String, rate: Int) {
        rates[Pair(from: from, to: to)] = rate
    }

    func rate(from: String, to: String) -> Int {
        if from == to {
            return 1
        }

        guard let rate = rates[Pair(from: from, to: to)] else {
            fatalError("未対応の通過です")
        }
        return rate
    }
}
Pair.swift
class Pair: Hashable {
    private let from: String
    private let to: String

    init(from: String, to: String) {
        self.from = from
        self.to = to
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(from)
        hasher.combine(to)
    }

    static func == (lhs: Pair, rhs: Pair) -> Bool {
        return lhs.from == rhs.from && lhs.to == rhs.to
    }
}
TDD_SwiftTests.swift
import XCTest
@testable import TDD_Swift

class TDD_SwiftTests: XCTestCase {
    func testMultiplication() {
        let five: Money = Money.dollar(amount: 5)
        // XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2))
        XCTAssertEqual(Money.dollar(amount: 10), five.times(multiplier: 2).reduce(bank: Bank(), to: "USD"))
        // XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3))
        XCTAssertEqual(Money.dollar(amount: 15), five.times(multiplier: 3).reduce(bank: Bank(), to: "USD"))
    }

    func testEquality() {
        XCTAssertTrue(Money.dollar(amount: 5) == Money.dollar(amount: 5))
        XCTAssertFalse(Money.dollar(amount: 5) == Money.dollar(amount: 6))
        XCTAssertFalse(Money.franc(amount: 5) == Money.dollar(amount: 5))
    }

    func testCurrency() {
        XCTAssertEqual("USD", Money.dollar(amount: 1).currency)
        XCTAssertEqual("CHF", Money.franc(amount: 1).currency)
    }

    func testSimpleAddition() {
        let five: Money = Money.dollar(amount: 5)
        let sum: Expression = five.plus(addend: five)
        let bank: Bank = Bank()
        let reduced: Money = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 10), reduced)
    }

    func testPlusReturnSum() {
        let five: Money = Money.dollar(amount: 5)
        let result: Expression = five.plus(addend: five)
        let sum: Sum = result as! Sum
        // XCTAssertEqual(five, sum.added)
        XCTAssertEqual(five, sum.addend.reduce(bank: Bank(), to: "USD"))
    }

    func testResuceSum() {
        let sum: Expression = Sum(augend: Money.dollar(amount: 3), addend: Money.dollar(amount: 4))
        let bank: Bank = Bank()
        let result: Money = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 7), result)
    }

    func testReduceMoney() {
        let bank: Bank = Bank()
        let result: Money = bank.reduce(source: Money.dollar(amount: 1), to: "USD")
        XCTAssertEqual(Money.dollar(amount: 1), result)
    }

    func testReduceMoneyDifferentCurrency() {
        let bank: Bank = Bank()
        bank.addRate(from: "CHF", to: "USD", rate: 2)
        let result: Money = bank.reduce(source: Money.franc(amount: 2), to: "USD")
        XCTAssertEqual(Money.dollar(amount: 1), result)
    }

    func testIdentityRate() {
        XCTAssertEqual(1, Bank().rate(from: "USD", to: "USD"))
    }

    func testMixedAddition() {
        let fiveBucks: Expression = Money.dollar(amount: 5)
        let tenFrancs: Expression = Money.franc(amount: 10)
        let bank: Bank = Bank()
        bank.addRate(from: "CHF", to: "USD", rate: 2)
        let result: Money = bank.reduce(source: fiveBucks.plus(addend: tenFrancs), to: "USD")
        XCTAssertEqual(Money.dollar(amount: 10), result)
    }

    func testSumPlusMoney() {
        let fiveBucks: Expression = Money.dollar(amount: 5)
        let tenFrancs: Expression = Money.franc(amount: 10)
        let bank: Bank = Bank()
        bank.addRate(from: "CHF", to: "USD", rate: 2)
        let sum: Expression = Sum(augend: fiveBucks, addend: tenFrancs).plus(addend: fiveBucks)
        let result = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 15), result)
    }

    func testSumTimes() {
        let fiveBucks: Expression = Money.dollar(amount: 5)
        let tenFrancs: Expression = Money.franc(amount: 10)
        let bank: Bank = Bank()
        bank.addRate(from: "CHF", to: "USD", rate: 2)
        let sum: Expression = Sum(augend: fiveBucks, addend: tenFrancs).times(multiplier: 2)
        let result: Money = bank.reduce(source: sum, to: "USD")
        XCTAssertEqual(Money.dollar(amount: 20), result)
    }
}

まとめ

Swift で TDD ができて楽しかったです。
Swift TDD をしようとしている人の参考になれば嬉しいです。

参考

テスト駆動開発
関数型言語Elmでテスト駆動開発(第1~4章)
関数型言語Elmでテスト駆動開発(第5~7章)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【swift】xcodeの基礎ショートカット/文法

  • cmd + R : ビルドラン
  • ctr + E : 行の最後に移動
  • ctr + A : 行の最初に移動
  • cmd + shift + F(O) : プロジェクト内検索
  • cmd + クリック : そこへとぶ
  • ctr + i : インデントの調整(範囲選択が必要)
  • option + クリック : クラスを2画面ひらける
  • cmd + option + / : メソッドのコメント
sample.swift
// 変数
var
// 定数
let
// null
nill
// 数字を文字列に変換  
string(a)
// boolean
var a : Bool = true
// 関数(戻り値->int)
func myFunc(value:Int)->Int{  }
// 画像をセット
let image = UImage(named:Const.LogoImage)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[はじめてのiOSアプリ]xcodeで地図アプリを作成(その6)

はじめに

iOSアプリを作ってみたいけど
何から始めて良いのかわからない

とりあえず、
「やってみました」記事を参考に
地図アプリを真似てみようと思う

という記事の6回目です。

今回は、拡大・縮小ボタンを追加します。

  • ピンチイン・アウトあるから、拡大・縮小ボタンは不要では?
    • Simulatorでピンチアウト(Ctrl+Option+Click)するのは直感的じゃなく忘れがちじゃない?
    • 直感的なボタンがあれば、ピンチアウトの操作を忘れても影響ないから
    • 「やってみました」記事だし、ボタンを配置してみたかったから

ボタンを追加

  1. ボタンを配置
    • [Main storyboard]を表示する
      • 【なぜ?】
        • [MKMapView]の上にボタンを配置する前準備
    • メニューから[View]-[Show Library]を選択、表示されるウインドウで [button] と入力し [Button]を絞り込む
      LibButtonItem.png
      • 【なぜ?】
        • MapViewにボタンを配置したいから
        • 絞り込まなくても、探し出すことができればOK
    • [Button]をドラッグ&ドロップでボタンをMapViewに配置する(「拡大」「縮小」の2箇所)
      • 【なぜ?】
        • 「拡大」「縮小」のふたつでセットだから
        • 邪魔にならず、操作に影響しないような任意の場所に配置する
        • 今回は、画面に左上付近にした
          SetButton.png
    • ボタンの文字[Button]を変更
      • 【なぜ?】
        • 何をするボタンなのかを見ただけでわかるようにするため
        • 今回は「+」「ー」とした
          SetButtonRenamed.png
    • メニューから[View]-[Inspectors]-[Show Attribute Inspector]を選択
      • 【なぜ?】
        • ボタンの文字サイズを変更したいから
          ShowRightSide.png
    • [MapView]に表示されているボタンを選択し、[Attribute Inspector]に表示されている文字サイズを好きなサイズに変更
    • サイズ変更に合わせ、ボタン位置も調整
      • 【なぜ?】
        • デフォルトだと小さく見にくいから、見やすいように大きくする
        • ついでに、Boldにもした(お好きないように、ご自由に)
          ModButtonSize.png
  2. ボタンの関連づけ

    • (以前の記事にも書いたように)[ViewController.swift]と[Main storyboard]を同時に表示する
      • 【なぜ?】
        • [Main storyboard]と[ViewController.swift]が同時に表示されていると[ボタン]の関連づけが容易にできるため
    • Ctrl+クリックで「+」ボタンのOutletを表示
      • 【なぜ?】
        • ボタンのタップとプログラムを関連づけるため(前準備)
          ButtonOutlet.png
    • [Touch Down]の右に表示されている○印を[ViewController.swift]にドラッグ&ドロップ

      • ドロップ場所は、以下の場所
      ViewController.swift
      var locationManager: CLLocationManager!
      
      // ここ付近にドラッグ&ドロップ    
      override func viewDidLoad() {
          super.viewDidLoad()
          // Do any additional setup after loading the view.
      
      • 【なぜ?】
        • ボタンのタップとプログラムを関連づけるため
          EventDD.png
    • 接続情報の入力を促すアイアログが表示されるので[Name]に[clickZoomin]と入力

      • 【なぜ?】
        • ボタンをタップしたときに実行する処理の名称ををプログラムで記述するため
      • ここまでの処理で、「+」ボタンのタップとプログラムの関連づけが完了
        InputConnectionInfo.png
    • 同様に「ー」ボタンにも同じような操作([Name]は[clickZoomout]とする)を実施

      • 【なぜ?】
        • 「ー」ボタンでも同じような操作を行うため
        • ViewController.swiftの関連する部分は、以下のようになっているはず
    ViewController.swift
    class ViewController: UIViewController, CLLocationManagerDelegate {
        @IBOutlet var mapView: MKMapView!
        var locationManager: CLLocationManager!
    
        @IBAction func clickZoomin(_ sender: Any) {
        }
        @IBAction func clickZoomout(_ sender: Any) {
        }
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
    
  3. ボタンに対応する処理を記述

    • ViewController.swiftの該当部分を以下のように修正

      • 【なぜ?】
        • 今回は、ボタンをタップしたことが判別できればOKだから
      ViewController.swift
       @IBAction func clickZoomin(_ sender: Any) {
           print("[DBG]clickZoomin")
       }
       @IBAction func clickZoomout(_ sender: Any) {
           print("[DBG]clickZoomout")
       }
      
  4. テスト実行

    • Simulatorを起動し、「+」ボタンや「ー」ボタンを押してみる
    • ボタンを押すたびに、Xcodeのログに位置情報に混ざり[clickZoomin][clickZoomout]出力された
    log
    [DBG]latitude : 37.33124551
    [DBG]clickZoomout
    [DBG]longitude : -122.03073097
    [DBG]latitude : 37.33121136
    [DBG]clickZoomin
    [DBG]clickZoomout
    [DBG]longitude : -122.03072292
    [DBG]latitude : 37.33117775
    [DBG]clickZoomin
    [DBG]longitude : -122.03071071
    [DBG]latitude : 37.33114614
    [DBG]clickZoomout
    [DBG]longitude : -122.03069859
    

今回の到達点

  • 画面に「拡大」「縮小」ボタンを配置することができた
  • ボタンをタップすると、デバッグメッセージが表示された

連載

  1. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その1:プロジェクト作成)
  2. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その2:地図表示)
  3. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その3:位置情報取得)
  4. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その4:位置情報と連携した地図表示)
  5. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その5:アプリアイコン設定)
  6. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その6:拡大・縮小ボタン追加)
  7. [はじめてのiOSアプリ]xcodeで地図アプリを作成(その7:地図を拡大・縮小)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

忘備録-Swiftのオプショナル型

趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。

参考文献

この記事は以下の書籍の情報を参考にして執筆しました。

if-let文

if-letでunwrapするときに複数の変数を指定できる

var hoge: String? = "hoge"
var fuga: String? = "fuga"
if let h = hoge, let f = fuga {
    print("nilじゃないよ")
}

guard文

guardの後に宣言した条件を満たさない場合、else節の処理を実行

func hogehoge() {
    //let hoge: String? = "hoge"
    let hoge: String? = nil
    guard hoge == nil else{
        //guardの条件じゃ無いなら下記の文が実行される。この場合nilじゃ無いなら実行
        print(hoge!)
        return
    }
    print(hoge)    //nil
    return
}
hogehoge()

nil合体演算子

ある変数がnilだった場合に別の値をセットしたいとき

let hoge: String? = nil
print(hoge ?? "hogehoge")    //hogeの値がnilなら"hogehoge"を使う

有値オプショナル型

let hoge: String! = "hoge"
let fuga: String? = "fuga"
let message1:String = hoge
let message2:String = fuga    // error unwrapしないといけない

有値オプショナル型はコンパイラが値の評価の時に特別扱いするだけで、オプショナル型と変わらない。

let message3:String! = fuga
let message4:String? = hoge

失敗のあるイニシャライザ

初期化に失敗した場合return nilをして処理を修了できる。

struct Hoge {
    var hoge: String
    init?(hoge: String?) {
        guard let h = hoge else{
        return nil
        }
    self.hoge = h
    }
}
let fuga = Hoge(hoge: nil)
if let f = fuga{
  print(fuga!.hoge)
} else {
  print(fuga)    // nil
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftのオプショナル型

趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。

参考文献

この記事は以下の書籍の情報を参考にして執筆しました。

if-let文

if-letでunwrapするときに複数の変数を指定できる

var hoge: String? = "hoge"
var fuga: String? = "fuga"
if let h = hoge, let f = fuga {
    print("nilじゃないよ")
}

guard文

guardの後に宣言した条件を満たさない場合、else節の処理を実行

func hogehoge() {
    //let hoge: String? = "hoge"
    let hoge: String? = nil
    guard hoge == nil else{
        //guardの条件じゃ無いなら下記の文が実行される。この場合nilじゃ無いなら実行
        print(hoge!)
        return
    }
    print(hoge)    //nil
    return
}
hogehoge()

nil合体演算子

ある変数がnilだった場合に別の値をセットしたいとき

let hoge: String? = nil
print(hoge ?? "hogehoge")    //hogeの値がnilなら"hogehoge"を使う

有値オプショナル型

let hoge: String! = "hoge"
let fuga: String? = "fuga"
let message1:String = hoge
let message2:String = fuga    // error unwrapしないといけない

有値オプショナル型はコンパイラが値の評価の時に特別扱いするだけで、オプショナル型と変わらない。

let message3:String! = fuga
let message4:String? = hoge

失敗のあるイニシャライザ

初期化に失敗した場合return nilをして処理を修了できる。

struct Hoge {
    var hoge: String
    init?(hoge: String?) {
        guard let h = hoge else{
        return nil
        }
    self.hoge = h
    }
}
let fuga = Hoge(hoge: nil)
if let f = fuga{
  print(fuga!.hoge)
} else {
  print(fuga)    // nil
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

忘備録-Swiftの構造体

趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。

参考文献

この記事は以下の書籍の情報を参考にして執筆しました。

構造体の初期化

カスタムイニシャライザ : 構造体ごとに定義できる独自のイニシャライザ
カスタムイニシャライザが定義されてないとき、自動的にシンプルなイニシャライザが使える。

既定イニシャライザ : 構造体の各プロパティに初期値が指定されている場合、型名(構造体の名前)()と書くことでインスタンスを生成できる。
struct Book {
    var name = "hoge book"
    var page = 125
}
var fuga = Book()
print(fuga.name, fuga.page)    // hoge book 125
全項目イニシャライザ : 各プロパディ名を引数として書き連ねる。
var dogBook = Book(name: "dog book")
print(dogBook.name, dogBook.page)    // dog book 125
var oneBook = Book(page: 1)
print(oneBook.name, oneBook.page)    // hoge book 1

全項目イニシャライザを使用する場合はプロパティに初期値を設定する必要がない。
その代わりに初期値が設定されていないプロパティは必ず引数として値を渡す必要がある。

struct Book {
    var name : String
    var page = 125
}
var fuga = Book(name: "hoge book")    // hoge book 125
print(fuga.name, fuga.page)
var dogBook = Book(name: "dog book")    // dog book 125
print(dogBook.name, dogBook.page)
var oneBook = Book(page: 1)    // error

定数に構造体を代入した場合

定数に代入された構造体のインスタンスは、各プロパディの値を変更できない。
この制約は構造体が値型であるためであり、クラスのインスタンスとは違う。(クラスのインスタンスは後ほど勉強します。)

struct Book {
    var name : String
    var page = 125
}
var hoge = Book(name: "hoge book")
hoge.name = "fuga book"
print(hoge.name, hoge.page)    // fuga book 125
let fuga = Book(name: "hoge book")
fuga.name = "fuga book"    // error

構造体に定数プロパティを宣言する場合

最初の一回しか値をセットできない。

struct Book {
    let name : String = "book"
    var page = 125
}
var hoge = Book(name: "hoge book")    // error
struct Book {
    let name : String
    var page = 125
}
var hoge = Book(name: "hoge book")
hoge.name = "fuga book"    //error

カスタムイニシャライザ

initを使って宣言

struct Book {
    var name : String
    var page : Int
    init() {
        name = " hoge book"    //全てのプロパディに対して値を設定しないとエラー
        page = 125
    }
}
var hoge = book()
print(hoge.name, hoge.page)

複数個のイニシャライザ

引数やラベル名を変えて複数定義できる。

struct Book {
    var name : String
    var page : Int
    init() {
        name = "hoge book"
        page = 125
    }
    init(n: String, p: Int) {
        name = n
        page = p
    }
}
var hoge = book()
print(hoge.name, hoge.page)     //hoge book 125
var dogBook = book(n: "dog book", p: 1)
print(dogBook.name, dogBook.page)    //dog book 1

ネスト型(付属型)

ネスト型 : ある構造体、(クラス、列挙型)を構成、使用するために、その構造体と密接に関連する型(構造体)を定義する場合、構造体の一部として宣言できる。

struct SimpleBook {
    var name : String
    var page : Int
    init() {
        name = "hoge book"
        page = 125
    }
    init(n: String, p: Int) {
        name = n
        page = p
    }
}
struct SellBook {
    typealias Book = SimpleBook    //上の構造体
    var book : Book
    var price : Int
    init(name: String, page: Int, price: Int) {
        book = Book(n: name, p: page)
        self.price = price    //プロパティ名と引数ラベルの名前が被っているのでself必須
    }
}
var hoge = SellBook(name: "hoge book", page: 125, price:50000)
print(hoge.book.name, hoge.book.page, hoge.price)    //hoge book 125 50000

構造体のメソッド

構造体にもメソッドが定義できる。

struct Book {
    var name : String
    var page : Int
    init() {
        name = "hoge book"
        page = 125
    }
    init(n: String, p: Int) {
        name = n
        page = p
    }
    func toMessage() -> String{
        "\(name)\(String(page)) ページです。"
    }
}
var hoge = Book()
print(hoge.toMessage())    //hoge book は 125 ページです。

構造体の内容を変更するメソッド

Swiftでは同じ構造体のインスタンスを出来るだけメモリで共有しておく方法を採用しているため、構造体の要素が変更されると共有方法も変える必要がある。
構造体の要素を変更するメソッドを定義する際には「mutating」というキーワードをfuncの前に置く。
コンパイラが要素を変更する処理を事前に知ることで、定数に格納されたインスタンに対しての呼び出し禁止、効率のいい実行コードの作成ができる。

struct Book {
    var name : String
    var page : Int
    var price : Int
    init() {
        name = "hoge book"
        page = 125
        price = 10000
    }
    init(name: String, page: Int, price: Int) {
        self.name = name
        self.page = page
        self.price = price
    }
    mutating func sell(b: Bool) {
        if b {
            price = Int(Double(price) * 0.8)
        } else {
            price = Int(Double(price) * 1.1)
        }
    }
}
var hoge = Book()
print(hoge.price)
hoge.sell(b: true)
print(hoge.price)

let fuga = Book()
fuga.sell(b: true)    //error

タイプメソッド

これまで定義してきたメソッドはインスタンスに対してのみ使用できる。「インスタンスメソッド」に相当するものである。

struct Hoge{
  func add(_ a:Int, _ b:Int) -> Int{
    a + b
  }
}
let hoge = Hoge()
print(hoge.add(3,2))    // 5
print(Hoge.add(3,2))    // error

これに対して、クラスメソッドに相当するメソッドを「タイプメソッド」という。
宣言するときはfunc の前にstaticをつける

struct Hoge{
  static func add(_ a:Int, _ b:Int) -> Int{
    a + b
  }
}
let hoge = Hoge()
print(hoge.add(3,2))    // error
print(Hoge.add(3,2))    // 5 

イニシャライザの中でメソッドを使いたい

イニシャライザの中ではプロパティの初期設定が全て完了していないと、インスタンスメソッドは使えない。

struct Book {
  var name : String
  var page : Int
  var message : String
  init() {
    name = "hoge book"
    page = 125
    message = self.toMessage(n: name, p: page)    // error
  }

  func toMessage(n name:String, p page:Int) -> String{
    "\(name)\(String(page)) ページです。"
  }
}

一つの方法としてタイプメソッドで定義すれば実装できる。

struct Book {
  var name : String
  var page : Int
  var message : String
  init() {
    name = "hoge book"
    page = 125
    message = Self.toMessage(n: name, p: page)    //Book.toMessageとも書ける
  }

  static func toMessage(n name:String, p page:Int) -> String{
    "\(name)\(String(page)) ページです。"
  }
}
var hoge = Book()
print(hoge.message)    //hoge book は 125 ページです。

構造体のプロパティ

Swiftのプロパティの種類
格納型プロパティ : 定数や変数が機能を提供する
計算型プロパティ : 値の参照と更新機能手続で構成
タイププロパティ(静的プロパティ) : 型(構造体名)に関連する情報を示すプロパティ(どんなインスタンスにも共通の値をセットするといい。メモリの節約になる。)

タイププロパティ

struct Book {
    static var name : String = "hoge book"
    static var page = 125
}
let hoge = Book()
print(hoge.name, hoge.page)    // error
print(Book.name, Book.page)    //hoge book 125

格納型プロパティの初期値を式で設定

インスタンスを生成するたびにnumberプロパティがインクリメントされる。

var serialNomber: Int = 0
struct Book {
  var name : String
  var page : Int
  let nomber : Int = Book.setNo()
  init() {
    name = "hoge book"
    page = 125
  }

  static func setNo() -> Int{
    serialNomber += 1
    return serialNomber
  }
}

var book1 = Book()
print(book1.nomber)    // 1
var book2 = Book()
print(book2.nomber)    // 2
var book3 = Book()
print(book3.nomber)    // 3

計算型プロパティ

他のプロパティの値を参照、計算して返す、代入することができる。

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
    get{
      Int(Double(price) * 1.1)
    }
    set{
      self.price = newValue
    }
  }
}

var hoge = Book(price: 2500)
print(hoge.name, hoge.price, hoge.tax)    // hoge 2500 2750
hoge.tax = 3400
print(hoge.name, hoge.price, hoge.tax)    // hoge 3400 3740

setを省略する場合getの記述を省略してかける。
getだけを記述することは出来ない。

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
      Int(Double(price) * 1.1)
  }
}
var hoge = Book(price: 2500)
print(hoge.name, hoge.price, hoge.tax)    // hoge 2500 2750
hoge.price = 3400    //hoge.taxは使えなくなるので注意
print(hoge.name, hoge.price, hoge.tax)    // hoge 3400 3740

特殊な設定

getにmutatingを記述 : プロパティの値を変えることができる。

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
  mutating get{    //getでプロパティ値を変えるときに記述
    price = Int(Double(price) * 1.1)
  }
  set{    
    self.price = newValue
  }
}

setにnonmutating : 背tで値を変更しないとき、構造体を定数に入れててもsetを呼べるようにする。

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
  get{
    Int(Double(price) * 1.1)
  }
  nonmutating set{
     print(newValue)
  }
}
let hoge = Book(price: 2500)
hoge.tax = 3400    //構造体が定数に格納されててもsetを呼べる

プロパティオブザーバ

プロパティオブザーバ : 格納型プロパティの値が変更されるのをきっかけに
起動できる処理

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
    willSet{
      print("new : \(newValue)")    // new : 108
    }
    didSet{
      print("old : \(oldValue)")    // old : 110
    }
  }
}

var hoge = Book(price: 100, tax: 110)
hoge.tax = 108

添字付け

添字付け:複数のプロパテがあるときに配列のように添字を使ってアクセスできるようにする方法

struct BookShelf {
    var book = ["hoge", "fuga", "inu"]
    var comic = ["JUMP", "oni"]
    var count = 5
    subscript(i: Int) -> String {
            return i < 3 ? book[i] : comic[i-3]
    }
}
let list = BookShelf()
for i in 0 ..< list.count {
print(list[i],terminator: " ")    // hoge fuga inu JUMP oni
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftの構造体

趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。

参考文献

この記事は以下の書籍の情報を参考にして執筆しました。

構造体の初期化

カスタムイニシャライザ : 構造体ごとに定義できる独自のイニシャライザ
カスタムイニシャライザが定義されてないとき、自動的にシンプルなイニシャライザが使える。

既定イニシャライザ : 構造体の各プロパティに初期値が指定されている場合、型名(構造体の名前)()と書くことでインスタンスを生成できる。
struct Book {
    var name = "hoge book"
    var page = 125
}
var fuga = Book()
print(fuga.name, fuga.page)    // hoge book 125
全項目イニシャライザ : 各プロパディ名を引数として書き連ねる。
var dogBook = Book(name: "dog book")
print(dogBook.name, dogBook.page)    // dog book 125
var oneBook = Book(page: 1)
print(oneBook.name, oneBook.page)    // hoge book 1

全項目イニシャライザを使用する場合はプロパティに初期値を設定する必要がない。
その代わりに初期値が設定されていないプロパティは必ず引数として値を渡す必要がある。

struct Book {
    var name : String
    var page = 125
}
var fuga = Book(name: "hoge book")    // hoge book 125
print(fuga.name, fuga.page)
var dogBook = Book(name: "dog book")    // dog book 125
print(dogBook.name, dogBook.page)
var oneBook = Book(page: 1)    // error

定数に構造体を代入した場合

定数に代入された構造体のインスタンスは、各プロパディの値を変更できない。
この制約は構造体が値型であるためであり、クラスのインスタンスとは違う。(クラスのインスタンスは後ほど勉強します。)

struct Book {
    var name : String
    var page = 125
}
var hoge = Book(name: "hoge book")
hoge.name = "fuga book"
print(hoge.name, hoge.page)    // fuga book 125
let fuga = Book(name: "hoge book")
fuga.name = "fuga book"    // error

構造体に定数プロパティを宣言する場合

最初の一回しか値をセットできない。

struct Book {
    let name : String = "book"
    var page = 125
}
var hoge = Book(name: "hoge book")    // error
struct Book {
    let name : String
    var page = 125
}
var hoge = Book(name: "hoge book")
hoge.name = "fuga book"    //error

カスタムイニシャライザ

initを使って宣言

struct Book {
    var name : String
    var page : Int
    init() {
        name = " hoge book"    //全てのプロパディに対して値を設定しないとエラー
        page = 125
    }
}
var hoge = book()
print(hoge.name, hoge.page)

複数個のイニシャライザ

引数やラベル名を変えて複数定義できる。

struct Book {
    var name : String
    var page : Int
    init() {
        name = "hoge book"
        page = 125
    }
    init(n: String, p: Int) {
        name = n
        page = p
    }
}
var hoge = book()
print(hoge.name, hoge.page)     //hoge book 125
var dogBook = book(n: "dog book", p: 1)
print(dogBook.name, dogBook.page)    //dog book 1

付属型

ネスト型 : ある構造体、(クラス、列挙型)を構成、使用するために、その構造体と密接に関連する型(構造体)を定義する場合、構造体の一部として宣言できる。

struct SimpleBook {
    var name : String
    var page : Int
    init() {
        name = "hoge book"
        page = 125
    }
    init(n: String, p: Int) {
        name = n
        page = p
    }
}
struct SellBook {
    typealias Book = SimpleBook    //上の構造体
    var book : Book
    var price : Int
    init(name: String, page: Int, price: Int) {
        book = Book(n: name, p: page)
        self.price = price    //プロパティ名と引数ラベルの名前が被っているのでself必須
    }
}
var hoge = SellBook(name: "hoge book", page: 125, price:50000)
print(hoge.book.name, hoge.book.page, hoge.price)    //hoge book 125 50000

構造体のメソッド

構造体にもメソッドが定義できる。

struct Book {
    var name : String
    var page : Int
    init() {
        name = "hoge book"
        page = 125
    }
    init(n: String, p: Int) {
        name = n
        page = p
    }
    func toMessage() -> String{
        "\(name)\(String(page)) ページです。"
    }
}
var hoge = Book()
print(hoge.toMessage())    //hoge book は 125 ページです。

構造体の内容を変更するメソッド

Swiftでは同じ構造体のインスタンスを出来るだけメモリで共有しておく方法を採用しているため、構造体の要素が変更されると共有方法も変える必要がある。
構造体の要素を変更するメソッドを定義する際には「mutating」というキーワードをfuncの前に置く。
コンパイラが要素を変更する処理を事前に知ることで、定数に格納されたインスタンに対しての呼び出し禁止、効率のいい実行コードの作成ができる。

struct Book {
    var name : String
    var page : Int
    var price : Int
    init() {
        name = "hoge book"
        page = 125
        price = 10000
    }
    init(name: String, page: Int, price: Int) {
        self.name = name
        self.page = page
        self.price = price
    }
    mutating func sell(b: Bool) {
        if b {
            price = Int(Double(price) * 0.8)
        } else {
            price = Int(Double(price) * 1.1)
        }
    }
}
var hoge = Book()
print(hoge.price)
hoge.sell(b: true)
print(hoge.price)

let fuga = Book()
fuga.sell(b: true)    //error

タイプメソッド

これまで定義してきたメソッドはインスタンスに対してのみ使用できる。「インスタンスメソッド」に相当するものである。

struct Hoge{
  func add(_ a:Int, _ b:Int) -> Int{
    a + b
  }
}
let hoge = Hoge()
print(hoge.add(3,2))    // 5
print(Hoge.add(3,2))    // error

これに対して、クラスメソッドに相当するメソッドを「タイプメソッド」という。
宣言するときはfunc の前にstaticをつける

struct Hoge{
  static func add(_ a:Int, _ b:Int) -> Int{
    a + b
  }
}
let hoge = Hoge()
print(hoge.add(3,2))    // error
print(Hoge.add(3,2))    // 5 

イニシャライザの中でメソッドを使いたい

イニシャライザの中ではプロパティの初期設定が全て完了していないと、インスタンスメソッドは使えない。

struct Book {
  var name : String
  var page : Int
  var message : String
  init() {
    name = "hoge book"
    page = 125
    message = self.toMessage(n: name, p: page)    // error
  }

  func toMessage(n name:String, p page:Int) -> String{
    "\(name)\(String(page)) ページです。"
  }
}

一つの方法としてタイプメソッドで定義すれば実装できる。

struct Book {
  var name : String
  var page : Int
  var message : String
  init() {
    name = "hoge book"
    page = 125
    message = Self.toMessage(n: name, p: page)    //Book.toMessageとも書ける
  }

  static func toMessage(n name:String, p page:Int) -> String{
    "\(name)\(String(page)) ページです。"
  }
}
var hoge = Book()
print(hoge.message)    //hoge book は 125 ページです。

構造体のプロパティ

Swiftのプロパティの種類
格納型プロパティ : 定数や変数が機能を提供する
計算型プロパティ : 値の参照と更新機能手続で構成
タイププロパティ(静的プロパティ) : 型(構造体名)に関連する情報を示すプロパティ(どんなインスタンスにも共通の値をセットするといい。メモリの節約になる。)

タイププロパティ

struct Book {
    static var name : String = "hoge book"
    static var page = 125
}
let hoge = Book()
print(hoge.name, hoge.page)    // error
print(Book.name, Book.page)    //hoge book 125

格納型プロパティの初期値を式で設定

インスタンスを生成するたびにnumberプロパティがインクリメントされる。

var serialNomber: Int = 0
struct Book {
  var name : String
  var page : Int
  let nomber : Int = Book.setNo()
  init() {
    name = "hoge book"
    page = 125
  }

  static func setNo() -> Int{
    serialNomber += 1
    return serialNomber
  }
}

var book1 = Book()
print(book1.nomber)    // 1
var book2 = Book()
print(book2.nomber)    // 2
var book3 = Book()
print(book3.nomber)    // 3

計算型プロパティ

他のプロパティの値を参照、計算して返す、代入することができる。

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
    get{
      Int(Double(price) * 1.1)
    }
    set{
      self.price = newValue
    }
  }
}

var hoge = Book(price: 2500)
print(hoge.name, hoge.price, hoge.tax)    // hoge 2500 2750
hoge.tax = 3400
print(hoge.name, hoge.price, hoge.tax)    // hoge 3400 3740

setを省略する場合getの記述を省略してかける。
getだけを記述することは出来ない。

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
      Int(Double(price) * 1.1)
  }
}
var hoge = Book(price: 2500)
print(hoge.name, hoge.price, hoge.tax)    // hoge 2500 2750
hoge.price = 3400    //hoge.taxは使えなくなるので注意
print(hoge.name, hoge.price, hoge.tax)    // hoge 3400 3740

特殊な設定

getにmutatingを記述 : プロパティの値を変えることができる。

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
  mutating get{    //getでプロパティ値を変えるときに記述
    price = Int(Double(price) * 1.1)
  }
  set{    
    self.price = newValue
  }
}

setにnonmutating : 背tで値を変更しないとき、構造体を定数に入れててもsetを呼べるようにする。

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
  get{
    Int(Double(price) * 1.1)
  }
  nonmutating set{
     print(newValue)
  }
}
let hoge = Book(price: 2500)
hoge.tax = 3400    //構造体が定数に格納されててもsetを呼べる

プロパティオブザーバ

プロパティオブザーバ : 格納型プロパティの値が変更されるのをきっかけに
起動できる処理

struct Book {
  var name: String = "hoge"
  var price: Int
  var tax : Int {
    willSet{
      print("new : \(newValue)")    // new : 108
    }
    didSet{
      print("old : \(oldValue)")    // old : 110
    }
  }
}

var hoge = Book(price: 100, tax: 110)
hoge.tax = 108

添字付け

添字付け:複数のプロパテがあるときに配列のように添字を使ってアクセスできるようにする方法

struct BookShelf {
    var book = ["hoge", "fuga", "inu"]
    var comic = ["JUMP", "oni"]
    var count = 5
    subscript(i: Int) -> String {
            return i < 3 ? book[i] : comic[i-3]
    }
}
let list = BookShelf()
for i in 0 ..< list.count {
print(list[i],terminator: " ")    // hoge fuga inu JUMP oni
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Appleの機械学習がヤバい

近年、機械学習はさまざまな分野で利用され注目を高めてきました。また、Firebaseをはじめてとした様々なクラウドサービスで手軽に利用できるほど便利なものになりました。そんな中、今年発表されたAppleの機械学習フレームワークCoreML3がすごかったため、今回CoreML周辺の情報を簡単にですがまとめてみました。

CoreMLとは?

Appleが2017年に発表した機械学習を扱うためのフレームワークであり、今年新たにCoreML3という名前で強力な機能の追加がなされた。機械学習モデルの仕様はオンデバイスで完結させることができ、さらにCreateMLというFrameworkを使い機械学習モデルを超簡単に作成することが可能。モデルを作成するためのCreateMLアプリはこちら
下記の画像でCoreMLの上位レイヤー部分にあたるフレームワークがCoreML3にのFeatuesです。

参照: https://developer.apple.com/documentation/coreml より

CoreMLを使用するメリットは?

CoreMLを使用するメリットは下記の通り大きく分けて4つあり、どれもオンデバイスで完結することによる効力を受けています。

Privacy

  • ネットワークを介してデータのやりとりする必要がないため、個人情報を守ることができる

Speed

  • デバイス上ですでにある学習モデルを使用するため、データ取得にかかる時間が超高速

No Server

  • デバイス上で完結するためサーバが必要なくなります

Available

CoreMLで何ができる?

実際にアプリにCoreMLが取り入れられているものを参考に見ていきましょう

  • BootFinder-App

    これは、写真を撮るだけでブーツの種類を判別し、そのブーツを販売しているオンラインストアを探すことができるアプリですが、ネットワークを介していないため、普通にサーバを介してデータを取得する場合とでは処理の速さは一目瞭然です。一度試しに使って見るとわかるのですが、超高速です。
    ml-2.jpg

  • HomeCourt-App

このアプリはバスケの練習を機械学習を用いることで、より効果的にトレーニングを行うことができるアプリです。

どこで機械学習が使われている?
  • バスケのドリブルを練習したい場合、ボール・人物・人物のバランスを検知し、トレーニングしたいポイントに応じてバーチャルな目標物が画面に出力され。目標物をタッチすることでトレーニングできる仕組み
どう作られてる?

下記の項目が主に使われている機能です。ここではだいたい機能がVisioフレームワークを使うことで実現されています。このVisionフレームワークについては別のセクションでも説明します。

CoreML-Framework

Model Creation

  • MLModel作成用のCreateML-Appで簡単に作ることができる(Xcodeでも可能)

Model生成の流れ

  1. DataCollection
  2. DataPreparation
  3. Training
  4. Testing

MLModelを作る上でポイント

Modelの精度の高まりを認知し、効果的にトレーニングするために、トレーニングの最初からテストを行って行くことが重要です。また、アプリ毎の用途に応じて必要なデータ量などは変わっていきますがImageClassificationの場合1クラス10枚から学習することが可能でしたので、手軽に使えそうです。そして、自分は機械学習についての知識は浅かったため知らなかったのですが、無視するべき関係のないデータ(ネガティブクラス)を含めることもModelを作成する上では重要みたいです。なぜ関係のないデータも必要かというと、例えば、りんご・いちごしか学習クラスがなかった場合、画像認識の際にどちらかのカテゴリーに結果が振り分けられますが、他のデータも学習させておくことで間違いの結果に当てはめてしまう確率を下げるということが狙いみたいですね。また、クラス(カテゴリー)のTrainingデータ量のバランスも大事で、できるだけバランスよく均等にデータ量を整えることが質の高いModelを作る上では重要です。

最後に

今回はあまり触れていませんでしたが、個人的にMLDataTableの活用の幅が広そうだなと感じました。例えば、好みの条件に合わせた地域の家賃相場を算出するアプリなどで、地域間取りキッチンの広さなどをそれぞれテーブルのカラムとし、それぞれの家賃を学習させることで、条件にあった地域の家賃がどのくらいなのかをネットワークを介さず高速に算出することができます。このようにCoreMLには色々な活用方法があり、まだまだ便利な機能があると思うので、何か面白そうな機能を見つけたらぜひ教えてください?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TabBarControllerをStoryboardReferenceを利用して導入してみた(Storyboardを分ける)

状況

Youtubeチャンネルの素材用にTinderの模擬アプリを作成する際、
TabBarControllerを使ったのですが
やり方が色々あってこんがらがったのと、いくつかつまづいたのでメモ。

やりたいこと

TabBarControllerStoryboardReferenceを利用して導入する
->StoryboardをTabBarController, 遷移先のVC(Tabの数ぶんStoryboardを用意 or 遷移先を1つのStoryboardにまとめる)でそれぞれ分けたいのでStoryboardReferenceを利用
->遷移先のVCにNavigationControllerを継承させたい

対策

  • TabBarController.storyboardを新しく作る->TabBarControllerを新しく作って、つながっている2つのVCを消しておく
  • StoryboardReferenceをTabの数だけ作る
  • 各々のStoryboardReferenceをTabと紐付けたい遷移先のVCと紐付ける
  • TabBarControllerのis initial viewcontrollerにチェックを入れる
  • TARGETMain interfaceTabBarControllerが存在するstoryboardに設定
  • info.pistStoryboard NameTabBarControllerが存在するstoryboardに設定
  • 以上を行ってもTabが表示されない場合はcleanや再起動諸々を試す(info.plistとかをいじるとcleanとかで解決するパターンが結構あるらしい。僕自身もcleanして解決しました。) スクリーンショット 2019-12-16 0.07.06.png

※一番左のタブから順々に紐づいていく

- 遷移先のVCごとにStoryboardがある場合:
StoryboardReferenceを選択しShow the Attributes inspector->Storyboard reference->StoryboardStoryboardの名前(ファイル名)を入力(Storyboard IDではない)
- 遷移先のVCを1つのStoryboardにまとめている場合:
上記に加えて、VCをStoryboard IDから参照が必要->Show the Identity inspector->Identity->Storyboard IDに入力

  • control+右クリックで各々のStoryboardReferenceに繋ぐ->Relationship Segueview controllersを選択 (※Modalなどではない)
  • 遷移先のVCでIs Initial Viewcontrollerの設定を確認(NCが紐づいているならそちらに設定する)
  • 遷移先のVCでIs Initial Viewcontrollerに設定したものにTabBar Itemを挿入

以上。

参考

[Xcode7] 「Unknown class xxxxx in Interface Builder file.」が出る - Qiita

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】Arrayからユニークな順序付きリストへの変換を超絶シンプルに書く

SwiftでArrayから重複なしの順序を保ったままのリストを生成したい!

と思って調べてみると、この辺の記事が参考になりますが、reduce()を使って愚直に全てをなめていく実装が散見されます:eyes:

今回はもっとスマートさを求めてシンプルに書けないか考えていきます:muscle:
(よりシンプルに書くことに特化しているのでパフォーマンスは気にしてません:yum:

Setは使える?

SwiftにはSetがあります。が、答えはノーです。
なぜなら、Setは重複しない順序を持たないリストだからです。
やってみます。

let array: [String] = ["hoge", "fuga", "hoge", "piyo", "piyo"]
// 最初に出てきた順でユニークになってほしい -> ["hoge", "fuga", "piyo"]

print(Set(array)) // "["fuga", "piyo", "hoge"]\n"
print(Set(array)) // "["piyo", "hoge", "fuga"]\n"
print(Set(array)) // "["piyo", "hoge", "fuga"]\n"

毎回結果が変わってしまいました。これでは順序を保つという条件が保証できません:sob:

順序付きのSetってないの?

Swift用はSet一択のようで、存在しないようです。
ですが、Foundation FrameworkにはNSOrderedSetという昔ながらのImmutableな順序付きSetのクラスが現在もサポートされています。

NSOrderedSetを使って順序付きのユニークリストを作る

さっそく書いていきましょう。

import Foundation

extension Array {
    func unique() -> [Self.Element] {
        return NSOrderedSet(array: self).array as! [Self.Element]
    }
}

こんな感じに書けるかと思います。

どうでしょう、めちゃくちゃシンプルですよね:sparkles:
NSOrderedSetに入れて吐き出すだけです!

NSOrderedSetはObjective-C時代のものなので、インターフェースがNSArray時代のように[Any]となってしまいます。
そのため、入力時も[Any]として扱われ、出力ももちろん[Any]として返ってきてしまいます。

そこで、SwiftのArrayのextensionとしてメソッドを用意し、Elementの型を入出力で縛ってあげることによって、使用する側は型安全でSwiftらしく書けるようにしています。
as!のような汚い実装はこうしてextensionの中に隠蔽してしまいましょう:mask:

では、最後に結果を確認していきます。

let array: [String] = ["hoge", "fuga", "hoge", "piyo", "piyo"]

print(array.unique()) // "["hoge", "fuga", "piyo"]\n"
print(array.unique()) // "["hoge", "fuga", "piyo"]\n"
print(array.unique()) // "["hoge", "fuga", "piyo"]\n"

いかがでしょうか?
呼び出しもシンプルで今回の目的を達成することができました:clap:
こちらは順序をサポートしているので、何度実行しても結果は変わりません!

P.S.

  • Advent Calendarに登録しようと思っていたのにすっかり忘れてしまったので、普通の投稿です(笑)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RxSwift MVVM における ViewModel 設計

自分は RxSwift で ViewModel はどうやって設計していくか下記三つ分けて書いていこうと思います。

  • Immutable
  • テストのしやすさ
  • プロトコルの読み取りやすさ

Immutable

  • 可変を避けるため struct を使う。
  • Observable, PublishSubject など値が流れ流けど不可変にする。
    • Protocol には var で定義されるが、struct では let で宣告します。

テストのしやすさ

テストしやすく、 mock しやすくするため、 外部リソースはできれば全て DI できるようにします。
例えばデータ処理、API 叩きなどが書いてある struct や class らはコンストラクタの引数で渡します。
そして、 ViewModel 自身の中身に、全域的なオブジェクトやクラスを直接使用すること、できればしないようにします。

プロトコルの読み取りやすさ

  • インプットとアウトプットを分けて定義する

RxSwift コンポーネントの使い分け

  • インプットは PublishSubject を使う
  • アウトプットは Driver を使う
    • 出口は UI なので Driver の方が良い
    • Driver はコンストラクタで初期化する

実装例

import RxSwift
import RxCocoa

protocol MyViewModelInput {
    var titleChanged: PublishSubject<String?> { get }
    var contentChanged: PublishSubject<String?> { get }
}

protocol MyViewModelOutput {
    var titleLength: Driver<Int> { get }
    var contentLength: Driver<Int> { get }
}

typealias MyViewModelType = MyViewModelInput & MyViewModelOutput

struct MyViewModel: MyViewModel {
    let titleChanged = PublishSubject<String?>()
    let contentChanged = PublishSubject<String?>()

    let titleLength: Driver<Int>
    let contentLength: Driver<Int>

    init() {
        titleLength = titleChanged
          .map { title in
              guard let title = title else { return 0 }
              return title.count
          }
          .asDriver { _ in .empty() }


        contentLength = contentChanged
          .map { content in
              guard let content = content else { return 0 }
              return content.count
          }
          .asDriver { _ in .empty() }
    }
}

ViewModel 以外は?

アウトプットがある場合、相手は UI 出なければ Observable や Single でしておいて大体問題ないと思います。

わかりにくい、説明不足ところや間違えたところあればぜひコメント欄にコメントしてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む