My Favorite Things - Coding or die.

とある技術者の経験記録、的な。

Swift3 例外メモ(try、try!、try?、do-catch)

try?の仕組みとか曖昧になってしまったのでメモ。

例外のスロー

  • 例外をスローする関数はthrowsを宣言(書く位置は引数宣言の直後)
  • スローする例外はErrorプロトコルを実装している必要がある
enum MyError: Error {
    case notFound
    case fail(String)
}

func fatalError() throws -> String {
    throw MyError.fail("fatal error.")
}

呼び出し元

すべての例外の補足

  • do { try throws宣言された関数呼び出し } catch { ... }といった構文で例外をキャッチ
  • throws宣言された関数を呼び出すときには、頭にtrytry!try?のいずれかをつける
  • catch { ... }またはcatch let error { ... }ですべての例外をキャッチ(switchdefaultみたいな)
  • enumのようにエラーの種類から網羅性はチェックされないので、網羅するにはすべての例外を補足するcatchが必須
func catchAllErrors() {
    do {
        try fatalError() // `throws`宣言された関数を呼ぶには`try`が必要
    } catch MyError.notFound { // エラーの種類を記述
        // ...
    } catch MyError.fail(let message) { // enumの関連値はパターンマッチで取得できる
        // ...
    } catch let error { // enumと違って網羅するにはこれが必須
        // ...
    }
}

部分的に例外を補足

  • すべての例外を補足しない場合は、関数の宣言にthrowsを記述する必要がある
func catchSomeErrors() throws { // 例外を網羅できていないので`throws`宣言が必須
    do {
        try fatalError()
    } catch MyError.notFound {
        // ...
    }
}

例外を強制的に無視する(try!)

  • try!を使うと例外を無視できる(do-catchが不要)
  • ただし例外が発生したときにはクラッシュする(強制アンラップと同じ挙動)
func forceCall() {
    try! notError() // 例外を無視する(が、発生したときはクラッシュする)
}

例外を安全的に無視する(try?)

  • try?を使うと、try!と同じように例外を安全に無視できる
  • try!と違い、例外が発生した場合でも単純に無視される
  • 呼び出した関数に戻り値がある場合は、戻り値がnilとなる(失敗した、というニュアンスか)
func safeCall() {
    try? fatalError() // 戻り値がない場合はセーフな呼び出し
    let s1 = try? fatalError() // => nil
    let s2 = try? someString() // => some string
}

まとめ

個人的にこんな感じで考えたらよいのかなという指針。

  • 発生した例外に興味がなければ、try?を使って例外を無視する
  • 発生した例外に興味がある場合は、do { try xxx() } catch { ... }を使って補足
  • 入力値が完全に固定など、確実に例外が発生しないと断言できる場合はtry!を例外的に使う(かも)

個人的にはEitherあるいはResultのほうが好きなのだけれど、シンタックスシュガーがよく出来ているので場合によっては例外の使用を検討しても良いのかも。

Vagrantfile: Ubuntu14.04 で Swift製Webフレームワーク「Vapor」を使える状態にする

Swift製のWebフレームワークである「Vapor」をUbuntuに入れてみた。
github.com

せっかくなのでVagrantfileを書いてみた。(たぶんこれが一番楽だと思います)

ちなみにVagrantについてはドットインストールの動画が分かりやすいです。
http://dotinstall.com/lessons/basic_vagrant

環境

手順

作業ディレクトリ作成

$ mkdir ubuntu-server # 適当なディレクトリを作成
$ cd ubuntu-server
$ curl -O https://gist.githubusercontent.com/YusukeHosonuma/54a2f4f294e87cf0dbc87b7420b5ae1c/raw/c181f41b05f2a7dddbe452dd5119b5dcbd857a96/Vagrantfile

VM起動+ssh接続

$ vagrant up
$ vagrant ssh

Vaporプロジェクト作成・ビルド

$ vapor new hello
$ cd hello/
$ vapor build

ビルドがわりと長い・・・依存関係の解決に時間かかってる?

実行

$ vapor run serve
Running hello...
No preparations.
Server 'default' starting at 0.0.0.0:8080

Tomcatと同じ8080ポートで起動されている模様。

動作確認

ホストマシン(Macとか)で以下のURLをブラウザで表示。 http://192.168.33.19:8080

真っ白な画面の中央に以下のロゴが表示されればOK。 f:id:yu_dotnet2004:20170102150015p:plain

簡単でしたね。

Vagrant + CentOS7 で、RustのWebフレームワークRocketを発射する

Linuxとか環境面詳しくないのでメモ。

Rocketは最近出たRustのWebフレームワーク。
https://rocket.rs

環境

VirtualBox: 5.0.30
Vagrant: 1.9.1
CentOS: 7.1
Rust: 1.16.0-nightly
Rocket: 0.1.2

ちなみにこれを書いている時点で最新のCentOSは7.3だったけれど、vagrant upで立ち上げたら途中で止まってしまったので断念。

VagrantでCentOS7を立ち上げる

まずは発射台を準備。

$ mkdir launch-pad
$ cd launch-pad

Boxイメージは、Chef社がOSSとして公開してる「Bento」を使う。
(弁当箱とは誰がうまいことを)
https://atlas.hashicorp.com/bento

$ vagrant init bento/centos-7.1
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

Vagrantfileは以下のようにしてプライベートネットワークとして接続できるようにしておく。

Vagrant.configure("2") do |config|
  config.vm.box = "bento/centos-7.1"
  config.vm.network "private_network", ip: "192.168.33.11", auto_config: false
end

原因はよく分かっていないのだけれど、auto_config: falseを設定しておかないと、立ち上げたVM上で正しくIPアドレスが振り当てられなかった。

VMイメージを起動。(Boxの取得から始まるので時間かかるよ!)

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
...

SSHで接続。

$ vagrant ssh
-bash: warning: setlocale: LC_CTYPE: cannot change locale (UTF-8): No such file or directory
[vagrant@localhost ~]$

基本設定

ファイアウォールを切ってしまう。(仮想環境なので)

$ sudo systemctl stop firewalld
$ sudo systemctl disable firewalld

SELinuxが無効か確認。今回は無効になっているのでそのまま。

$ getenforce
Permissive

Rustのインストール

Rocketでは構文拡張を利用するため、NigthlyなRustが必須。(2016/12/30時点)

Rust1.14.0で公式ツールとなったrustupをインストールする。 https://rustup.rs

$ curl https://sh.rustup.rs -sSf | sh
info: downloading installer
...

選択肢が表示されるが、とりあえずデフォルトの1)を選択。

Current installation options:

   default host triple: x86_64-unknown-linux-gnu
     default toolchain: stable
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation

Rust is installed now. Great!

To get started you need Cargo's bin directory in your PATH environment variable. Next time you log in this will be done automatically.

To configure your current shell run source $HOME/.cargo/env

パスを設定してくださいと言われるので、.bash_profile に記述しておく。

# Rust
source $HOME/.cargo/env

再読込して現在のシェルに反映させておく。これでRustが使えるようになった。

$ source ~/.bash_profile
$ rustc --version
rustc 1.14.0 (e8a012324 2016-12-16)

そしてRustを最新版(Nightly)に変更。

$ rustup default nightly
$ rustc --version
rustc 1.16.0-nightly (4ecc85beb 2016-12-28)

Hello, world

Rocket用のプロジェクトを作成して、そこに移動。

$ cargo new hello-rocket --bin
     Created binary (application) `hello-rocket` project
$ cd hello-rocket

Cargo.toml の依存ライブラリに以下を追加。

[dependencies]
rocket = "0.1.2"
rocket_codegen = "0.1.2"

src/main.rs を以下のように編集。このあたりは公式ドキュメントどおり。

#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

fn main() {
    rocket::ignite().mount("/", routes![index]).launch();
}

そしてビルド・実行・・・するがエラーになる。

$ cargo run
...

error: aborting due to previous error

error: Could not compile `unicase`.

To learn more, run the command again with --verbose.

どうやらgccが入っていないのが原因らしいのでインストール。

$ sudo yum -y install gcc
...

今度こそロケットを打ち上げられた。

$ cargo run

Finished debug [unoptimized + debuginfo] target(s) in 79.19 secs
 Running `target/debug/hello-rocket`
🔧  Configured for development.
=> listening: localhost:8000
=> logging: Normal
🛰  Mounting '/':
=> GET /
🚀  Rocket has launched from http://localhost:8000...

VM内でcurlを叩くとちゃんと動いていることが分かる。

$ curl localhost:8000
Hello, world!

80ポートで起動する

ここからはあんまり正攻法じゃない。 ので、とりあえずこの方法でうまくいった程度の話。

Rocketのドキュメントを見ると、Productionで実行するには以下のようにすればよいと書いてある。

$ sudo ROCKET_ENV=staging cargo run
sudo: cargo: command not found

が、エラーとなる。

これは環境変数PATHがsudo時に引き継がれないのが問題らしい。

$ env | grep PATH
PATH=/home/vagrant/.cargo/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin
$ sudo env | grep PATH
PATH=/sbin:/bin:/usr/sbin:/usr/bin

で、調べるといろいろと情報が出てくる。

CentOS で sudo 時に実行ユーザーのPATHを引き継ぐ http://qiita.com/ogwmtnr/items/3ec2fab50d069a3cf335

sudoで環境変数を引き継ぎたい http://mikio.github.io/article/2012/03/10_sudo.html

が、何を試しても一向にcargoのパスが引き継がれない。

仕方ないのでルートユーザーでもrustupを入れてしまう。(同様なので方法は割愛)

$ su -
# rustupのインストールと設定

ちなみにパスワードは「vagrant」。

そしてそのままルートユーザーで以下を実行すると、80ポートで起動する。

# ROCKET_ENV=staging cargo run

🔧  Configured for staging.
    => listening: 0.0.0.0:80
    => logging: Normal
🛰  Mounting '/':
    => GET /
🚀  Rocket has launched from http://0.0.0.0:80...

ホストマシンからブラウザでアクセスしてみるとちゃんと動いている。 http://192.168.33.13

GET /:
    => Matched: GET /
    => Outcome: Succcess
    => Response succeeded.
GET /favicon.ico:
    => Error: No matching routes for GET /favicon.ico.
    => Warning: Responding with 404 Not Found catcher.
    => Response succeeded.

ちなみにデフォルトの8000ポートだと以下のように指定してもアクセスできなかった。
http://192.168.33.13:8000/

ファイアウォールを切ったので問題ないと思ったのだが、何か設定がマズイのかもしれない。

終わり

Linuxとか環境構築とかあんまり経験ないので思ったよりもずっとしんどかった。 まぁしかしVagrantとか楽でいいなと思った。

とりあえず以下は解決したい課題。

  • ルートユーザで実行しちゃってる。(本番環境なら大問題)
  • 8000ポートでホストマシンから接続できるように。
  • Linuxとか詳しくなる。

Swift3: バージョン番号を楽に比較する

なんか面倒だったので書いた。

これがSwift使いの戦い方。たぶん。

Swiftコンパイラに詳しくないけど、こういった構造体でラップするだけのコードって最適化後はゼロコストになるのかしら?
Haskellnewtypeはゼロコストだったはずだけど)

SwiftでApplicative Style

書いたことなかったので書いてみた。

我流なのでいい感じに出来てるかは微妙だけどApplicative Styleがパーサ周りで有効活用できるという確かな実感は得られた。 Swift 4で(?)ジェネリクスが強化されれば、Haskellの型クラスのようにProtocolでもジェネリクスが使えるようになるのかしら?

iOSオールスターズ2に参加してきた感想

帰ってブログ書くまでがイベントって言われたので書くよ! eventdots.jp

ブログなので当然ですけれど、私が思ったことをそのまま書いてます。 (とりあえずファーストインプレッションをダンプしてきます)

あ、登壇者の皆様、本当に貴重な情報をありがとうございました!

RxSwift in Practice

  • 状態をストリームとして表現するのは良いと思った。
  • しかし、やっぱ殆どのケースでは大げさ=オーバースペック感は否めない。
  • インジケータ非表示にするの忘れる、くらいであればテストで十分感あり(発表でもあったけど)。
  • やはり状態がたくさんある状態で、最新のViewに更新される必要がある、みたいなケースが活きるのではないか?
  • コードレビューをランダムに回すのは良いアイディアだと思った。
  • 学習コストが高い、っていうのは素直に認めなきゃと思う。

VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法

  • ギャグ満載。
  • Fat VC=「肥満体質な」VCって表現は分かりやすかった。(コードの臭い匂い的な意味とも通じる)
  • クリーンアーキテクチャは大げさ過ぎる気がしたけど、ViewController/Presenterまわりの実装の仕方はありだと思った。
  • 「君の名は」を見ておくべきだった?

Type-safe URL Routing in Swift

  • サーバサイドの話かと思ったけれど、URLスキーム起動とかでiOSも該当する。なるほど。
  • ApplicativeFunctorでパース処理を書けるのは良いと思った。(JSONパーサとかでも活用されてるけど)
  • 途中から全く分からん状態!
  • 関数型もっと勉強しよう・・・

Using PDF in iOS

  • 濃い・・・。
  • でも実際にPDFを扱う必要が出た場合に、間違いなく参考になる貴重な情報。

Xcode8で開発はどうかわったのか

  • 変わってない。
  • Visual DebuggingでAutoLayoutの制約が確認できるようになったのは良いと思った。
  • FPS Performance Gauge、いずれ使ってみたい。
  • プロファイラも進化したなぁ、と思う。

これから始めるProtocol Buffers導入

  • Googleが考えた、シリアライズフォーマット。
  • バイナリで高速(サイズがJSONの半分以下に)
  • .protoファイルからSwiftコードを生成できる
  • ありだとは思うけれど、本当にパフォーマンスが重要な場面以外では導入は控えて、JSONにすべきだと思った

DIを前提にしたiOSアプリの設計

  • DIコンテナを使うべきか、というとそこまでは判断できないけど、DI設計はありだと思った。
  • 依存性注入を意識したコードスタイルになっていれば、DIコンテナを導入しなくても、テストなどは相当楽になるはず。
  • Storyboardからインスタンス生成されるとプロパティインジェクションになっちゃうのは確かに。(個人的にもコンストラクタインジェクション派)

Swiftらしい表現を目指そう

  • すごく分かりやすかった。
  • イニシャライザは文章っぽく書かなくてもいいってのは知らなかった。
  • プロトコルHaskellで言えば型クラス)を意識したコードを書くのは、もうちょっと日頃意識したいと思った。

おわり

  • 楽しかった!
  • 途中でピザの匂いで食欲そそられた。
  • イベント会場キレイだった。
  • 関数型はもっと勉強していくよ!