Swift Advent Calendar 2015 で勉強してみる(6日目)
Swift プログラミング
Swift Advent Calendar 2015 が暖かい感じに盛り上がっていて好きだったので、自分もこれを眺めながら Swift をあれこれ勉強してみることにしました。
その6日目です。
そこで、開催されている4つの Swift Advent Calendar の内容を拝見して、そこからいろいろ浮かんだことを綴ってみようと思います。
- Swift Advent Calendar 2015
- Swift その2 Advent Calendar 2015
- Swift その3 Advent Calendar 2015
- Swift(一人) Advent Calendar 2015
Swift Advent Calendar
Swift Advent Calendar の6日目は takebayashiさん の Swift で Sinatra っぽく Web アプリケーションを書いて Linux で動かす というお話でした。
アプローチとしては OS X でも Linux でも動く簡単な Web アプリケーションを Swift で作ってみた という内容みたいです。
Sinatra
そもそも Sinatra ってなんだろう?と思ったんですけど、どうやら関数を書くだけで、簡易 Web サーバーが立ち上がってリクエストを捌けるようになる仕組みのようでした。
そう聞いて Linux の xinetd みたいなものを想像したんですけど、どうやらもっと簡単で、担当する URI を引数で受け取って文字列を返すコードを作って実行するだけで簡易 Web サーバーが待機状態になるところまで行ってくれる様子でした。
Swiftra
そして、その Sinatra をモチーフにして takebayashi/swiftra というライブラリを作成されたそうです。
この README を見ると実感できるのですけど、とてもシンプルにコードが書けるようになっていて、これだけで自由な Web サーバーが動かせるというのが素敵です。
これが OS X でも Linux でも動かせるとあれば、ちょっとした Web サーバーが何かと必要になる人にとってはとても嬉しいものになりそうですね。
願わくば Apple TV とかの On-Demand Resources テストとかにも使えるように ATS に対応してくれたりすると嬉しさがいっそう増しそうです、なんて少し無茶振りしてみたりして。
さておき、この Swiftra で使っている takebayashi/http4swift にも個人的にちょっと興味をそそられました。
Swift Package Manager
ところで、この Advent Calendar 記事内の Swiftra を利用して Web アプリケーションを作成する で Swiftra を Swift Package Manager を使って導入する 話が綴られています。
Swift Package Manager といえば Swift がオープンソースになったときに一緒に登場したもので Swift コードをパッケージとして頒布できる ものという噂は聞いたのですけど、それ以上のことを知らなくて分からない部分が多かったので Advent Calendar 記事を参考にしながら自分なりにも調べたことを綴ってみることにします。
依存定義の追加行
Advent Calendar 記事を読んでいて、記載されていた 下記のように依存定義を1行追加するだけで
というところが、最初はよくわかりませんでした。
これはどうやら、まず Swift Package Manager 対応のパッケージを使いたいときに、それを使う Swift コードも Swift Package Manager を想定したフォルダー構造にして、その中に Package.swift
を用意して、内容を記載することになる様子でした。
そしてそのとき、それが使うパッケージを次のように dependencies
で指定することになるようです。
dependencies: [
.Package(url: "https://github.com/takebayashi/swiftra.git", majorVersion: 0)
]
実際に試してみる
Swift Package Manager にしても Swiftra にしても、複雑な手順は何も求められない印象ですし、実際に動かしてみたら実感が湧くだろうと思って試してみることにしました。
嬉しいことにすぐに試せる Swiftra サンプルコード を用意してくれていたので、それをビルドしようとしたのですけど、次のようなエラーで進めなくなってしまいました。
<unknown>:0: error: no such file or directory: 'build'
この問題自体は単純で Xcode 7.2 に付属の Swift 2.1.1 が Swift Package Manager に対応していないというだけだった様子です。
swift-build
ただ、オープンソースの apple/swift を手順に従ってビルドして、それを使うようにしても、次のようなエラーになって上手くいきませんでした。
error: unable to invoke subcommand: ./build/Ninja-DebugAssert/swift-macosx-x86_64/bin/swift-build (No such file or directory)
Swift Package Manager を使うためには、どうやら Swift.org - Download Swift で公開されている "Swift Open Source Xcode Toolchain" パッケージをインストールする必要がある様子でした。
Swift パッケージのビルド
これをインストールすると、関連するファイルが /Library/Developer/Toolchains/swift-latest.xctoolchain
にインストールされました。
ここの usr/bin
に入っている swift
を使うと、同じくそこに収められている swift-build
を使って Swift パッケージをビルドすることができました。
/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin/swift build
先ほどの Swiftra のサンプルコード内に用意された README
に従いつつ swift build
のところだけ上記のように実行したら、次のような表示がされて、無事にビルドが通った様子です。
Cloning Packages/swiftra Cloning Packages/http4swift Compiling Swift Module 'http4swift' (6 sources) Linking Library: .build/debug/http4swift.a Compiling Swift Module 'swiftra' (5 sources) Linking Library: .build/debug/swiftra.a Compiling Swift Module 'swiftraexample' (1 sources) Linking Executable: .build/debug/swiftra-example
この出力から窺えるように、swift build
を実行したフォルダーにある Package.swift
に記載されている依存パッケージが、そのさらに依存するものも含めて自動的にダウンロードされて、それらが静的ライブラリとしてビルドされ、そして最後に、カレントフォルダーの Package.swift
で指定されているパッケージ名で実行ファイルが生成された様子でした。
生成された実行ファイルはドットで始まる名前の .build
フォルダーに入れられるので、ターミナルで普通に ls
を実行しても表示されなかったりしますけど ls -a
で確認すると見つかります。
Swiftra サンプルアプリを実行してみる
ここまで出来たら、あとは Advent Calendar 記事内の Swiftra アプリケーションを実行する のとおりに試してみると、記載のとおり、しっかりとサンプルコードが動いてくれました。
やってみると、その手軽さがなおさら実感できますね。個人的には Swift Package Manager 周りの学習も出来てさらに良かったです。
Swift その2 Advent Calendar 2015
Swift その2 Advent Calendar 2015 の6日目は syamaokaさん の Swift で HTML を構文解析して、UITableView にマッピングする方法 というお話でした。
具体的には mattt/Ono というライブラリを使って HTML を解析し、特定のタグごとにテーブルビューセルを作って表示するということが実現されています。
所感
この Advent Calendar 記事内では、そんな mattt/Ono ライブラリを使って実際にそれを実現する上での要所がピンポイントに紹介されていて、このライブラリの基本的なところを学んだ後の、実際にどう使ったらいいかみたいなところを手早く垣間見れるように構成されていました。
実際のソースコード全体は bpyamasinn/MappingToTableView に公開されているので、この Advent Calendar で要所を掴めば、コード全体を読み進めるのもずいぶん楽な印象でした。
個人的に XML の解析周りに興味があるところだったのですけど、この mattt/Ono も XML に対応しているみたいなので、自分もちょっとこのライブラリを気にしてみたくなりました。
列挙型の String な Raw 値について
ところで完全に余談なのですけど、Swift の列挙型で Raw 値を String
型にした場合、たしか Swift 2 あたりから、特に何も明記しないと列挙子と同じ文字列が設定されるようになりました。
そのため Advent Calendar 記事内の PermitTag
列挙型の定義を次のように書き換えても、今のコードと全く同じに動作します。
enum PermitTag: String {
case none = ""
case p
case h1
case h2
case h3
}
上記の例の none
のように、列挙子と違う Raw 文字列を当てたい時だけそれを明記すればいいので、今回のサンプルコードでサポートしたいタグがたくさん増えたときには記載がずいぶん楽になるはずです。
Swift(一人) Advent Calendar 2015
Swift(一人) Advent Calendar 2015 は全日を shimesabaさん が埋め尽くすという素晴らしさですけれど、その6日目は class と static の挙動の違いを整理する というお話でした。
この話!自分はついつい、なんとなく違いを想像して済ませていたところだったので、とても嬉しいテーマです。そのため、把握しきれていな部分がたくさん思い当たるので、今回の shimesabaさん の話を頼りに学習して、そこから思い描かれることを自分も追記してみたいと思います。
継承関係
まず、今回の Advent Calendar を見て、記事内の まとめ にあるとおりの認識であっててホッとしました。
static
が静的(つまり不動のもの)で class
がクラス継承を意識したものと捉えれば、オーバーライドの可否と適用できる型については自然に捉えられそうです。
class var
が使えないことについては、エラーメッセージが class stored properties not yet supported in classes
であることを見ると 何故できないのか
とかを考える以前に とりあえずできないことになっている
と捉えておくのが良さそうです。
static var
については、型そのものに備わるプロパティとして、構造体に対するそれと同じに捉えておけばきっと大丈夫でしょう。
余談:保持型プロパティの継承について
ところで完全に余談ですけど、クラスに実装した普通の var
で実装した保持型プロパティは、そのクラスを継承したサブクラスで、保持型プロパティとしてオーバーライドすることはできなくなっています。
同じ名前のプロパティでオーバーライドしたいときには、サブクラスでは計算型プロパティとしてオーバーライドすることになります。
class MyClass {
var value: Int = 0
}
class SubClass : MyClass {
override var value: Int {
get {
return super.value + 1
}
set {
super.value - 1
}
}
}
もし、保持型の class var
が使えるようになったとしたら、きっとこんな感覚で使えるようになるのかなって想像しつつ。
続きの考察
ここからは Advent Calendar 記事で培った知識を踏まえて、そこから思い浮かんだ疑問についていろいろ試してみることにします。
メタタイプ型での利用について
型情報を変数に取得して、そこから呼び出す場合に static
と class
に違いが出てきたりするのかな?と思って調べてみました。
要は こちら
で紹介した required init
みたいな感覚になるのかな?という話です。
結果的には、どちらとも同じように扱うことができました。
class MyClass {
static var staticValue: Int = 1
class var classValue: Int { return -1 }
}
class MySubClass : MyClass {
override static var classValue: Int {
get {
return super.classValue * 2
}
}
}
MyClass.staticValue
MyClass.classValue
let type = MyClass.self
type.staticValue // OK (1)
type.classValue // OK (-1)
let subtype = MySubClass.self
subtype.staticValue // OK (1)
subtype.classValue // OK (-2)
サブクラスを親のメタタイプ型から扱う場合
次に、サブクラスの型情報を親の型で扱った時に static var
がどういう扱いになるかが気になってきます。
そこで、先ほどのクラス定義はそのまま、次のテストコードを追加してみたところ、先ほどと同じ実行結果になりました。メタタイプは継承関係もちゃんと把握してくれている様子です。
let basetype: MyClass.Type = MySubClass.self
basetype.staticValue // OK (1)
basetype.classValue // OK (-2)
static var は final class var ということ?
そんな検証をしていたところ、試しに static var
を継承先でオーバーライドしてみようとしたところで、次のような興味深いエラーメッセージが表示されるのに気がつきました。
error: class var overrides a 'final' class var
具体的には、次のようなコードを書いてこのエラーが発生しました。
class MyClass {
static var staticValue: Int { return 1 }
}
class SubClass : MyClass {
// error: class var overrides a 'final' class var
override static var staticValue: Int {
return super.staticValue * 2
}
}
つまり、内部的には static var
が final class var
として扱われている可能性が窺えます。
そこで、次のように static var
を final class var
に書き換えてみたところ、継承先のオーバーライドでエラーが出るのは想像がつきますけど、その時のエラーが先ほどの static var
の時と全く同じになりました。
class MyClass {
final class var staticValue: Int { return 1 }
}
class SubClass : MyClass {
// error: class var overrides a 'final' class var
override class var staticValue: Int {
return super.staticValue * 2
}
}
じゃあ、もし内部で final class var
とみなされているとすると
親クラスでは final
を付けない class var
で定義して、サブクラスで static var
でオーバーライドしてみるとどうなるんだろう?
と思ってやってみると、エラーなくコンパイルが通りました。
class MyClass {
class var staticValue: Int { return 1 }
}
class SubClass : MyClass {
override static var staticValue: Int {
return super.staticValue * 2
}
}
さらにそれを継承した先で static var
を継承することはできない感じで、このことからも内部的な扱いは
static var
は final class var
と等価
という見方をしていて大丈夫そうな感じがしました。
そんな捉え方をしてみると、具体的な好例はまだ思い浮かばないものの static var
と class var
の使い分けはイメージできるようになるかもしれませんね。もっとも、観察から得られた class var
と final class var
という差でこれらの違いを捉えた時に、それが必ずしも理屈的に適切かどうかは分かりませんけど。
プロトコルでの class var
の扱い
プロトコルには static var
だけ定義できて class var
は定義できなくなっています。
protocol MyProtocol {
static var staticValue: Int { get } // OK
class var classValue: Int { get } // NG
}
この性質は、プロトコルを AnyObject
から継承したり、プロトコルに @objc
ディレクティブを付けても変わりません。
このとき素直に次のようにして、クラス型の MyClass
を MyProtocol
に準拠することができます。
protocol MyProtocol {
static var staticValue: Int { get }
}
class MyClass : MyProtocol {
static var staticValue: Int { return 7 }
}
そしてここで static var
のところを class var
に書き換えてみても、問題なくビルドすることができました。
class MyClass : MyProtocol {
class var staticValue: Int { return 7 }
}
もちろん素直な class var
なので継承することも可能です。そしてそれを特定のプロトコル型として扱った時でも、ちゃんと継承関係を意識した値が得られる様子でした。
protocol MyProtocol {
static var staticValue: Int { get }
}
class MyClass : MyProtocol {
static var staticValue: Int { return 7 }
}
class MySubClass : MyClass {
static var staticValue: Int { return super.staticValue * -1 }
}
let type: MyProtocol.Type = MySubClass.self
type.staticValue // -7
所感
以上の感じから static var
と class var
の特徴的なところをいったんおさえてしまえば、あとは特に例外もなく安定して扱えそうな印象でした。
構造体などの値型とクラス型との間で生まれる差も class var
が final class var
として扱えるところを意識すると、ちょうど こちら
の Self
のところで紹介した final
付きのクラスが構造体と同等に扱える(将来の継承を気にしたコードを書かなくて良くなる)のと同じ感じになるのも、しっくりくるような印象でした。