気に入って眺めていっている技術ブログ「その Swift コード、こう書き換えてみないか」の中から、今回は for ... in
ループと forEach
関数の違いを見比べるところを眺めていきます。前回にもおおよそ眺めたところではありますけれど、これらの特徴を把握しておくとより扱いやすくなりそうなのと、ブログ内で丁寧に特徴が整理されている印象なので、もう少ししっかり見ていくことにしますね。よろしくお願いします。
—————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #263
00:00 開始 00:35 今回の展望 01:02 forEach は使いにくい印象 01:54 forEach メソッドの特色 04:07 underestimatedCount って使う? 05:29 for ⋯ in と forEach は同じように使える 07:46 制御構文なのか、クロージャーなのか 08:13 return で考えてみるとわかりやすい 09:48 forEach の実装をイメージしてみる 15:49 制御構文を自在に操るための必要な知識 17:44 Sequence.forEach の実装を見ておく 19:03 rethrows の動きを知っておくのもオススメ 19:55 クロージング ——————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #263
では始めていきますね。今日は前回見た for-in
と forEach
の挙動の違いを意識しようという内容です。まず、個人的にお気に入りのブログを紹介しておきますね。このブログには、前回お話しした for-in
と forEach
の違いについて書かれていて、意外と参考になります。
前回のお話でも、「基本的には for-in
を使う」といった意見がちらほら出ていました。私も同じ感じで、 forEach
は少し使いにくい気がしています。この使いにくさの理由についても含めて、今回は説明していきます。あくまでも私の主観ですが、どちらのほうが良いかという認識をブラッシュアップするのに、この説明が役立つと感じています。
前回は for-in
と forEach
の挙動が同じだけれど、実際にはこんな違いがあるよと簡単に話して終わりました。フォールディングされている実装部分はまだ見ていませんが、このリンクが興味深いので見ておきました。
例えば、あるアプリの開発者が言っていましたが、 forEach
では与えられたクロージャをシーケンスのそれぞれの要素に適用していく方法を使っており、 for-in
ループと同じ順番で適用していくと書かれています。これは興味深いですね。普段はあまり気にしていないけれど、この点が重要だと思います。ただし、 for-in
とは違う点もあり、再度確認が必要です。
また、 iOS 8
から forEach
が使えるようになりました。例外処理にも対応していて、この部分は for-in
ループと少し違うため、エラーハンドリングに関しても考慮に入れる必要があります。しかし、 forEach
が throws
や rethrows
に対応していれば、 for-in
ループ自体もエラー処理に対応しますし、逆にエラー処理を気にしないクロージャを渡せば問題ありません。このように柔軟な設計になっています。
他のエラー処理周りの違いについても、 for-in
と forEach
で使い心地が変わってくるので、それぞれの用途に応じて使い分けるのが有効かもしれません。
ついでに他の話題として、イルメディティブやアンダーエスティメイティッドカウントについても簡単に触れてみます。アンダーエスティメイティッドカウントはシーケンスの要素数を返すメソッドで、例えば、配列の reserveCapacity
の際には有用かもしれませんが、デフォルト実装が 0 を返すことがあるので注意が必要です。
とりあえずは forEach
と for-in
の話に戻りますが、 forEach
メソッドと言語組み込みの for-in
構文について、両方とも同じ書き方でループを回す方法が存在します。ただし、 forEach
を使う場合と for-in
を使う場合には、それぞれの特性を理解しておくことが重要です。
以上が今回の内容です。次回も引き続き、他の例とともに for-in
と forEach
の違いを深掘りしていく予定です。 まず、for-in
構文について説明します。for-in
は、例えば次のような書き方ができます。
for year in 1...4 {
// 処理
}
と、
(1...4).forEach { year in
// 処理
}
これらはいずれも同じように動作します。どちらを使っても同じ結果を得ることができるというわけです。具体的な例として、print
文を使用することで違いを確認することができます。
次に、forEach
メソッドとfor-in
構文の違いについて考察します。最も重要な点は、forEach
がメソッドであり、クロージャを引数として受け取る点です。これに対し、for-in
は言語構文です。
クロージャ内でreturn
を使用すると、クロージャから抜け出します。これは、クロージャが記述されている関数から抜け出すことを意味します。例を挙げると、次のようなイメージです。
func exampleFunction() {
(1...4).forEach { year in
if year == 2 {
return
}
print(year)
}
print("Finished")
}
この場合、クロージャ内のreturn
はクロージャのみを抜け、関数の呼び出し元には戻りません。クロージャが終了し、次のイテレーションに移行します。
また、for-in
構文の場合、次のような実装を考えてみます。
for element in self {
body(element)
}
このbody
関数が終了すると次の要素に移行します。
他の実装例として、イテレーターを利用する方法があります。
var iterator = self.makeIterator()
while let element = iterator.next() {
body(element)
}
このようにfor-in
とforEach
には似たような動作がありますが、使い分けが必要です。特にクロージャ内でのreturn
やブレークの処理については、状況に応じて適切な方法を選択することが重要です。 他にも4-1の実装のアイデアはあるのでしょうか。イテレーターを取る方法やインデックスを使って回す方法、自分の要素を使う方法などがあります。しかし、これは無理矢理で全然意味がない気がします。例えば、8
を使って8でループを書いたりして、この中でマップに対してトランスフォームでボディを渡してあげることが考えられます。これだとモドリティが返ってこようとしますが、今回の場合はVoid
の型が返ってきてしまうので、警告が出るかもしれないです。もし警告が出たら対処しなければなりません。個人的にはお勧めしない方法ですが、こういう書き方もできます。これと同じようなイメージで、分かりやすい方法を見つけることが大事です。
それから、どれが最適かを考えて自分なりの答えを見つけていくのが良いでしょう。重要なのは、渡した関数を呼び出してそれが終了するかどうかです。ここにreturn
を書くのと全然違います。return
を書くとループを抜けますが、この動きをイメージできるかどうかが重要です。
これを理解すれば、return
やbreak
、continue
といった制御構文がどこで働くのかを理解できます。この辺りを理解することで、for-in
やforEach
の理解が簡単になります。同じように制御フローを理解していければ、コード全体をイメージしやすくなります。最初の学習コストは高いですが、その後の応用が効きやすくなります。
forEach
の実装を見ると、for-in
で表現しているのがわかります。基本的な構文ですね。for-in
の方がシンプルで基礎的な感じがします。
あとは制御構文の特徴とクロージャーの特徴に絞られます。そこで、関数とエラーハンドリングの話も出てきます。例えば、スローしたときにどこに出るかなども考える必要がありますが、大丈夫でしょう。
コメントでも指摘されているように、rethrows
の話もあります。これは勉強会の別の機会でも取り上げられるかもしれませんが、rethrows
は重要なポイントです。
次はforEach
以外の話を見ていきましょう。for-in
とforEach
についてはまだ話すことがありそうですが、今日はこれくらいにしておきます。お疲れ様でした。ありがとうございました。