Swift Advent Calendar 2015 で勉強してみる(4日目)
Swift プログラミング
Swift Advent Calendar 2015 が暖かい感じに盛り上がっていて好きだったので、自分もこれを眺めながら Swift をあれこれ勉強してみることにしました。
その4日目です。
そこで、開催されている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 の4日目は yonekawaさん の SwiftFluxで複雑な状態の変化を予測可能にするiOSアプリ開発 というお話でした。
複雑な状態を管理するためのアプローチとして Flux というのがあるとのことで、ここではその実装のひとつ yonekawa/SwiftFlux についてのコンセプト的な紹介と、実践で使うコード例が中心になる様子でした。
Flux と Redux
そんな冒頭を拝見して、初日に教わった Redux がまず思い起こされました。
どちらとも予測可能な状態管理の仕組みとのことで、初日の Redux で印象的だったのが 読み取り専用の状態 というキーワードでしたけど、今回の冒頭を読んで Flux も同じ趣向のものなのかなと感じました。
2つの関係性
そんな Flux と Redux の関係性については、この Advent Calendar 記事内の SwiftFlux の今後 でも取り上げられていました。
これを見ると、なにやら Redux は Flux にはない制約が設けているとのことでした。つまり Flux はもう少しカジュアルに使えるということになるでしょうか。
余談ですけど、制約と聞くと Swift で制約のおかげで自由度が増すという体験をさせてもらったおかげで、自分の中では好印象な言葉でした。もっとも、適切な制約が課せられていればの話でしょうけど、ともあれもしかすると Redux は大きめな規模のプロジェクトで使うと威力を発揮するのかもしれません。
もっとも、必ずしも Redux の延長線上に Flux があるわけでもないみたいです。
Flux
初日の Advent Calendar Swift で Redux をさわってみる とこの Advent Calendar 記事内の 複雑な状態の変化を予測可能にするアーキテクチャ を見て思いを巡らせていたら、なんとなく 予測可能な状態管理 ということの意味合いが見えてきたような気がしました。
たしかにこうして処理の流れを1本の線で描く仕組みをスマートに強要できたとしたら これを更新したらこうなる みたいなものが明確になって、制御がかなり楽になりそうな予感がします。
図がわかりやすい
そんな冒頭に記載されている丁寧な図がまたわかりやすかったです。
この図は Flux の考え方に iOS アプリの UI 画面と UIViewController の処理の流れを重ね合わせたものだと思うんですけど、これが見事に綺麗な流れを描いているように見えます。
Web API を使った非同期処理のコードを書いていると本質的にはこの流れに似たものになっていくと思うんですけど、つまりこれを体系立てて抽象化ができれば、このレールにコードを乗せるだけで綺麗な流れが作れそうなことが、この図を見てたらよく見えてきました。
SwiftFlux
SwiftFlux では、行動を起こす時の Action に渡す情報を汎用パラメーターではなく型で明示できるのが嬉しそうです。そして Store では取り扱う状態をまとめて管理できるのと、どういう場合にリスナーに通知 (emit) するのかを制御できるのが特徴のように感じました。
気になった点としては、リスナーとして登録したハンドラーが呼び出されたときに、ハンドラーの中で自主的に Store を読みに行っているところが(予測可能という観点で)自由すぎるようにも感じましたけど、これは単に自分が Flux の思想を修得していないからかもしれません。
初見の印象
初見の印象としては Action を定義するときに受け取った dispatcher に対して手続き的なコードが必要なのと、Store を定義するときの準備がなんとなく多そうに感じました。
特に Action
の invoke
で dispatcher.dispatch
を呼び出すところは原則的なルールに思えるので、そうだとすればここを書かせることでコーディングミスを生む可能性が出てきてしまうのがもったいないようにも思います。それなら Result を返すメソッドを1つ実装したら、あとはよしなにやってくれる invoke
をプロトコル拡張で実装するのでもいいのかなと感じました。
同じように Store
の init
でも eventEmitter
に emit
するコードが必要になるので、コードを書き忘れてリスナーが呼ばれず悩むみたいなことも起こり得そうに思いました。この辺りは何らかの形で戻り値を emit するかを表す列挙型をひとつ用意して(またはシンプルに Event? を使って)それを戻り値で返したらよしなに emit してくれる感じにすると、最後の肝心なところを書き忘れずに済むので楽になるのかなもと思いました。
private(set)
あと、ちょっとした補足になりますけど、コード例の中で Store に状態を持たせるときに private var internalTodo: Todo?
と var todo: Todo? { get }
のふたつで持たせてますけど、ここをきっと private(set
var todo: Todo?) とまとめて定義しても大抵の場合はうまくいきそうです。
エラー処理
エラー処理はたしかに Advent Calendar 記事内の エラー処理 で述べられているように、迷うところかもしれません。
記事内の例では列挙型を使って Store に エラーの状態も含め、ビューの現状を示す状態 を持たせてその変化を通知することで、リスナー側のハンドラーでエラーを検出できるようにしていました。
妥当性は置いておいて、これを見た印象としては、せっかく Action が型で明確に扱えるのに、リスナー側ではその Action によって何がどう変わるかをドキュメントで把握して必要な状態を自発的に取得しないといけなくなりそうなところが、なんとなくもったいないようにも思えました。
NSNotification
ところで、初日の Redux や今日の Flux を見ていて、自分の目にはなんとなく Cocoa フレームワークでお馴染みの NSNotificationCenter
と仕組みや動きがそっくりなように感じました。
もっとも、これだけだと約束事が足りなすぎるのと、通知に自由度がありすぎて Swift では扱いにくい印象ですけど、先日に試作してみた ESNotification を使えば型安全な invoke や emit ができそうな気がするので、あとは読み取り専用な状態を手軽に扱える Store の仕組みと invoke から emit への橋渡し周りを上手く作れれば、良いことありそうな予感もしました。
ただ、先日に ESNotification
を試作していく中で、通知センターの欠点として 全体を把握するのがむずかしくなる
というのを思いついたところだったので、もしかすると筋違いなことを考えているかもわかりません。
把握がむずかしくなるということは、予測できなくなるということでしょうからね。それが invoke と emit とを結びつけることで解消されるのかも分かりませんけど、そんな先を自分が思い描くためには、実際にコードを書いて体験するか、もう少し Flux のことを知ってみないといけなそうです。
Swift その2 Advent Calendar 2015
Swift その2 Advent Calendar 2015 の4日目は ezuraさん の Swift 全予約語 (81語) の解説 というお話でした。
予約語 81 個をひとつひとつ挙げて紹介するというパワフルさで、観光地巡りみたいな楽しい記事に感じました。そんな冒頭の紹介の中で 個人的には indirect が一番好きです!
というところがまた素敵です。
Qiita だと右側に内容のインデックスが付けられますけど、それがまさに予約語の索引になっているので、予約語を眺めて知りたいときに便利そうです。
ちなみに自分が今、いちばん好きな予約語は rethrows
です。
そんな素敵な予約語たちですけれど、そんな中から気に留まったところを訪れてみたいと思います。Advent Calendar 記事内と重なる部分には触れていないので、併せてご覧ください。
deinit
クラス型でだけ使えて、構造体では使えません。
enum
列挙型を作る enum
は 幾つかの中からある一つの値を選ぶ
値を作りたいときに使うものですけれど、取り得る値をコンパイラが把握してくれるところが嬉しいですよね。
そんな enum
が switch
と出逢うと、その『網羅性』が最大限に発揮されてさらに素敵な世界が広がります。
extension
Advent Calendar 記事内の 振る舞いを追加します
という表現にトキメキました。
init
他にも init?
と書けて、初期化できなかった時に nil
を返す 失敗可能イニシャライザー
を定義することができます。
Swift 2 になって Error Handling が搭載されたため、失敗するかもしれないイニシャライザーを init?
で書くか init throws
で書くかは悩みどころです。
inout
同じ意味になるかまでは見てないですけど inout
パラメーターのところを UnsafeMutablePointer<SampleClass?>
にしても、同じ方法で受け渡しと更新ができたりします。
func inoutSampleForClassWithoutInout(pSampleClass: UnsafeMutablePointer<SampleClass?>) {
pSampleClass.memory = nil
}
Void ポインター
その後の手詰まり感がありますけど UnsafeMutablePointer<Void>
で、任意の型の参照を受けることもできます。
func sampleForVoidPointer(p:UnsafeMutablePointer<Void>) {
}
ちなみにこれを使うと 同じ型で揃えられた任意個のタプルを分解して Array に収める みたいなこともできたりします。(お勧めしてません)
UnsafePointer
ちなみに上記の UnsafeMutablePointer<T>
を UnsafePointer<T>
に変えても、呼び出し時に変数名に & を付けて渡せます。
ただ、渡す側では Mutable ではないのに var
で変数宣言しないといけないことと、受け取り側では Mutable ポインターではないので memory
を書き換えられないため、なんかチグハグなコードになります。
let
感覚的なお話ですけど、個人的には let
も var
と同じ『変数』なのかなと感じてます。
構造体と相性抜群です!
operator
個人的には恐怖のオペレーターです。
名前空間が効かないので、演算子を表現する関数がひとたび衝突すると import を諦めるしか逃げ場がないのと、演算子宣言が衝突すると優位性を勝ち取った方が負けた方の優先順位 (precedence) まで上書きしてくれたりします。
Let's Try!
自分のコードに次の定義を書き加えて 3 * 2 + 1
を計算してみましょう。
infix operator + {
precedence 200
}
private
クラス型で
private(set) var value:Int
みたいな定義をすると、簡単に読み取り専用プロパティーを実現できます。クラス型は値型と違って let
でも中のプロパティーが保護されないので、この書き方が結構便利に使えます。
typealias
なるほど、型エイリアスをプロトコルで使った場合は、それを "付属型" (associated type) と呼ぶんですね。良いこと教えて頂きました。
ちなみに付属型も前後関係で決定できるので、プロトコルを適用するときに記載を省略できます。
struct SampleStruct: SampleProtocol {
// 戻り値で使われている付属型 AssociatedType が Int に決まります
func sampleFunc(param: AssociatedType) -> Int {
return param + param
}
}
case
case
が where
と出逢うとさらに素敵になります。
また case
は guard
内でも活かせます。
guard case let x? = optionalSample else {
fatalError()
}
defer
defer
は複数定義できて、並列に並べた場合はスコープ離脱時にスタック順に呼び出されます。他の defer
の入れ子にした場合は defer
内をひとつのスコープと見て、内側の defer
が約束通り実行されます。
スコープを抜ける時に必ず処理される性格は try
と相性抜群です。
do
すごい!スコープを作るだけもできるんですね。
for
for
が where
と出逢うと、新しい景色が広がります。
let values = [1, 0, -5, 8, 3]
for value in values where value > 0 {
print(value)
}
as
as
演算子にはもうひとつ、型を明記するという役割があります。
// 整数リテラル 10 を Int32 型として使います。
let a = 10 as Int32
戻り値によるオーバーロードを解決したいときにも便利です。
func getValue() -> Int {
return 3
}
func getValue() -> Double {
return 3.14
}
getValue() as Int // 3
getValue() as Double /// 3.14
rethrows
rethrows
は 引数で受け取ったクロージャーが投げるエラー以外は投げない
という約束もするので、その関数を呼び出す側が どんなエラーが発生し得るか
を確実に把握できる利点もあります。
また rethrows
が付けられた関数を呼び出すときに、渡したクロージャーがエラーを投げる可能性がある場合は try
を付ける必要がありますが、クロージャーがエラーを発生させるはずがないなら try
が不要になるという性質もあります。
func test(f: () throws -> Void) rethrows {
try f()
}
let f1: () throws -> Void = { }
let f2: () -> Void = { }
try! test(f1)
test(f2)
super
ちなみに super
を変数に入れることはできないので、参照専用的な存在みたいです。
class MySubClass : MyClass {
func test() {
// ERROR: Expected '.' or '[' after 'super'
let s = super
}
}
Self
プロトコルの定義で使うと それを適用した型自身 を表現できます。
protocol MyProtocol {
func newInstance() -> Self
}
構造体であればその型そのものを意味しますけど、クラス型の場合は継承関係も含めた最終的な型を意味するため、継承されるかもしれない未来を見据えてインスタンスを返す必要があります。
そのために、実行時に自身の型情報を取得する dynamicType
と、未来永劫、存在することを約束された required init
と出逢う運命にあります。
protocol MyProtocol {
func newInstance() -> Self
}
class MyClass : MyProtocol {
required init() {
}
func newInstance() -> Self {
return self.dynamicType.init()
}
}
または final class
によって、未来を今ここで約束することでも実を結びます。このとき Self
は自分自身の型に書き換えます。
final class MyClass : MyProtocol {
func newInstance() -> MyClass {
return MyClass()
}
}
didSet
UIViewController
などでプロパティーに何か値をセットしたら、その内容を画面に反映したいときに便利です。
struct Item {
var name: String
var description: String
}
class ViewController : UIViewController {
@IBOutlet private var nameLabel: UILabel!
@IBOutlet private var descriptionLabel: UILabel!
var item: Item {
didSet {
self.nameLabel.text = self.item.name
self.descriptionLabel.text = self.item.description
}
}
}
ここで didSet
の処理は、そのプロパティーを、実装している型のイニシャライザー内で代入しても呼び出されません。実装している型のメソッド内で代入すると呼び出されます。イニシャライザー内で初期化完了後に呼び出した関数内で代入している場合は呼び出されます。
この性質は willSet
でも同様です。
ちなみに didSet
では、変更前の値を oldValue
変数で取得できます。また willSet
では、変更予定の値を newValue
変数で取得できます。そしてどちらの場合も、現在の値を取得するときは self.item
みたいに自身のプロパティーを直接参照します。
indirect
型をひとつ飛び越して再起になる場合にも indirect
が必要だったりするみたいです。
struct Box {
var value: SampleEnum
}
enum SampleEnum {
case Num(Int)
indirect case IndirectEnum(Box)
}
SampleEnum.IndirectEnum(Box(value: .Num(1)))
lazy
モジュールの最上位に定義した(モジュール内での)大域変数は、暗黙的に lazy
が定義されて、初めて使うタイミングでインスタンスが初期化されます。
optional
プロトコルで optional
が指定できる場面は、それが @objc
指定の場合に限られます。そしてちなみに @objc
を指定するには Foundation フレームワークをインポートする必要があります。
required
もしかすると、サブクラスにイニシャライザーの オーバーライドを強制する
と言うよりは サブクラスにもそのイニシャライザーの存在を約束させる
ものかもしれません。
継承
サブクラスで required init
の実装を上書きしなくても、いちばん近隣で実装されているものが使われる様子です。
class MyClass {
required init(value: Int) {
}
}
class MySubClass : MyClass {
}
let instance = MySubClass(value: 1)
隠蔽できない
オブジェクト指向の場合、継承先で継承元のイニシャライザーを隠蔽したりできますけれど、これが required init
の場合は隠蔽できなくなる様子です。
class MyClass {
required init(value: Int) {
}
}
class MySubClass : MyClass {
// ERROR: 'required' initializer must be as accessible as its enclosing type
private required init(value: Int) {
super.init(value)
}
}
let instance = MySubClass(value: 1)
required init の存在性
そして required init
は、それが定義された以降、継承されるすべての型で利用できることが保証されます。
そのため Advent Calendar 記事内の dynamicType
や Type
のところで紹介されている通り、型情報そのものから required init
が呼出せます。
class MyClass {
required init(value: Int) {
}
}
class MySubClass : MyClass {
}
let type = MySubClass.self
let instance = type.init(value: 10) // required init の場合に呼び出せる
もちろん dynamicType
で得られた型情報に対しても呼び出せます。
何れにしても required init
が付けられていないイニシャライザーは、たとえば MySubClass.init
みたいに型そのものからしか呼び出せないため、型情報を使って動的にインスタンスを作りたいときには必須になります。
プロトコルで規定したイニシャライザー
プロトコルでイニシャライザーを規定したとき、それはクラス型では required init
として定義することになります。
protocol MyProtocol {
init(value: Int)
}
class MyClass : MyProtocol {
// ERROR: Constructing an object of class type with a metatype value must use a 'required' initializer
init(value: Int) {
}
}
ただし、クラス定義で final
をつければ、それ以降の未来を約束する必要がなくなるので required
は不要になります。ただし親クラスで実装された required init
の場合は final
クラスでもオーバーライド時に required
指定が必要になります。
Swift その3 Advent Calendar 2015
Swift その3 Advent Calendar 2015 の4日目は mishimayさん の Swift の @noescape をもっと使おう というお話でした。
ちょっとしたメソッドを作るときにお世話になるキーワードです。そんな @noescape
の特徴と、それで得られるメリットが上手にまとめられています。
特徴
この Advent Calendar 記事では @noescape
の様子が読み切りやすいボリュームで書かれているので全体を一読するのがオススメですけど、その中でも特に端的にまとめられた その関数を呼ぶ関数よりもクロージャの生存期間が短いことを保証する
という言葉がとても見事な印象でした。
self
個人的には self
をついつい書く性格なのでメリットとは感じないですけど、そう、クロージャーを受け取る側で @noescape
が指定されていると、それに渡すクロージャーで self
が省ける特徴があります。
その理由の確かなところまでは判らにですけど、先ほどの その関数を呼ぶ関数よりもクロージャの生存期間が短いことを保証する から想像すると、自分自身 (self) を含むその中で使用するオブジェクトのすべてが生存することが、クロージャーが生存している間は保証されるため インスタンスをキャプチャーする必要がない のが理由ではないかと想像します。
そのあとは mishimayさん の想像されている通りで、普通のクロージャーでは self を含む、クロージャー内で使うオブジェクトはすべて(キャプチャーリストで扱いを明記しない限りは)生存を保証するために retain されます。
クロージャー内で self.
を書かないといけない理由もきっと察しの通りで、普通のクロージャーだと生存範囲が中で使われているオブジェクトの生存範囲を超える可能性があり、存在場所も self
の外で使われる可能性があるので、処理が self
に依存するときは、クロージャー内で明示的に self
を持って回らないといけない感じと想像してます。
パフォーマンス
パフォーマンスの向上も、おそらく上記の理由の通りで、クロージャーの内側で使うオブジェクトを retain しなくてすむ分、クロージャーを作るときのオーバーヘッドが減少するものと思われます。
そのほかにももしかして、この Advent Calendar 記事内の クロージャはどこへも保持されない という特徴から想像すると、クロージャーを確保しておくための変数確保が省略されたりするみたいな効果もあるかもしれません。
循環参照の予防
この @noescape
は Advent Calendar 記事内にある通り、循環参照の予防は大きなメリットになると思います。
小さな処理の関数ではそもそも循環参照が発生して困るようなことは起こらないと思いますけど、これがさらに、引数で受け取ったクロージャーを別の関数に渡すみたいなのを積み重ねて行ったとき うっかりクロージャーを保存して循環参照が発生する みたいなことを、その機能を設計している人がうっかり起こさないようにするという面で大いに効果を発揮しそうです。
@noescape クロージャーは変数に入れられない
というのも @noescape
を指定して受け取ったクロージャーは別の変数に入れようとするとコンパイルエラーになるためです。クロージャーの実行だけが許されます。
func test(@noescape f: () -> Void) {
// ERROR: @noescape parameter 'f' may only be called
let closure = f
}
@noescape クロージャーを受け取る関数にだけ渡せる
そんな @nonescape
なクロージャーですけど、同じく @noescape
が指定されている引数には渡すことができます。
func test(@noescape f: () -> Void) {
_test(f)
}
func _test(@noescape f: () -> Void) {
}
これはきっと渡した先でも、渡す前のスコープに存在するすべての生存が保証されている(要は渡した先の方がさらに生存が短い)ため渡しても問題がない、という理由で充分に説明がつきそうです。
@autoclosure
ちなみに @autoclosure
指定のクロージャーも @noescape
クロージャーとして扱われます。
そのため、それを別の変数に保存することはできず、即実行するか、次のように @noescape
が指定されたクロージャー引数に渡せるだけになります。
func test(@autoclosure value: () -> Int) {
_test(value)
}
func _test(@noescape f: () -> Int) {
}
Swift(一人) Advent Calendar 2015
Swift(一人) Advent Calendar 2015 は全日を shimesabaさん が埋め尽くすという素晴らしさですけれど、その第4日目は frame.size.width + 10 を frame.size.width + margin にした途端に動かなくなる理由 というお話でした。
タイトルを読んで そんなことがあるのか! と思いましたけど、なるほど、リテラルの扱いと型の不一致に起因するお話ですね。たしかにこれは普通に遭遇しそうですし、思いがけず遭遇したら我を忘れてしまいそうです。
リテラル周りは面白い!
思いがけないところからの問題発生に始まって、それをひとつひとつ紐解きながら探っていく様子が、読み物としてとても面白かったです。
そこから次第に Swift に於けるリテラル変換の仕組みの核心に迫って行くので、リテラルと型の関係を捉えきれていない人はぜひ読んでみて欲しい内容でした。これを読んでリテラルを知ると Swift コードの自由度の幅がいっそう広がります。
ちなみにこの話題は に開催される 集まれSwift好き!Swift愛好会 で話そうと思っている内容と丸かぶりな見込みです。笑