Page icon

第166回

2022/10/05
Empty
The Basics
Empty
Empty
Empty
Empty
Empty
Empty
12 more properties
今回は
オプショナルバインディング
の 
if var
表記や 
強制アンラップ
 周りを見ていきます。これまでにも幾度と見てきたところですけれど、別の話やオリエンテーションで間がだいぶ空いているので、再確認してみるのにちょうど良いタイミングな気がします。
これまでは脱線として話してきた話題でもあるので、今回は改めてそれらに主眼を向けて見ていきますね。よろしくお願いします。
今回はゆめみ社外に向けた公募はなかったので、社内メンバーでの開催になります。
——————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #166
00:00 開始 00:40 if let 省略表記のおさらい 02:15 if var 省略表記? 03:38 if var を使う上で気になるところ 04:59 Swift で var を使うことについて 05:39 書き換えの影響がどこまで及ぶか 10:15 var を使ったコードで語弊を含む可能性 11:58 if var なのに if let 省略表記? 12:25 シャドーイングしなければ語弊を含みにくい 13:45 if var が必要な場面を考えてみる 18:46 guard と var を組み合わせてみると良い感じかも? 21:53 if let 省略表記で guard を書く 25:27 if var で処理面でのコストはどうなる? 28:19 reduce に見るパフォーマンスチューニング 32:08 最適化を想定したパフォーマンス向上ではありそう 32:46 最適化が図られたときの reduce の処理を実際に書く 36:16 実際の reduce(into:) の定義 37:35 右辺値と値のコピー 40:39 本当にコピーコストが浮くのかどうか 41:27 再起呼出的に考えるか、手続き的に考えるかの違い? 42:57 var の存在も認めてあげるくらいの気持ちで 43:51 クラスなら割り当てコストを抑えられる? 45:23 クロージング ———————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #166
さて、再びオプショナルの話に戻りたいと思います。久しぶりですね。一応毎回ほんの少しだけ触れてはいますが、すぐに脱線してしまうことが続いていました。今日は久しぶりにオプショナルについて話しますが、脱線しないようにするという約束は全くできません。むしろ、脱線したほうがさまざまなことを得られたりするので、今回もそんな形で進めたいと思います。
まず、
if let
の表記について軽くおさらいしましょう。
if let
は変数名を使ってオプショナルバインディングを行うものです。この機能は変数を宣言し、それを元々あるスコープの変数から値を取って、同じ名前を使ったときにはシャドーイングまで行うというものです。
if let
ショートハンドは非常に強力な機能で、理解にはそれなりに高度な知識を要求されることがあります。これを使うことで、コードがよりすっきりと書けるようになるという利点があります。
今日はこの省略表記の話とはまた別で、
if var
という記法についても触れてみたいと思います。この記法も何回も話に出てきていますが、オプショナルバインディングといえば
if let
という発想が定着しがちです。しかし、
if var
という表記も可能です。この記法を覚えておくと、必要なときに使えるかもしれません。
if var
は、オプショナルに含まれる値を取り出す際に、その値を定数ではなく変数として扱えるというものです。普段あまり考えが及ばないかもしれませんが、書くときには注意が必要です。
if var
で宣言された変数はローカルスコープ用として宣言され、その外には影響を与えないという点です。そのため書く場所や使い方に注意が必要で、誤解や語弊を招く可能性があります。
また、
if var
を使うときは、アンラップ元のオプショナルの変数には適用されません。Swiftでは
var
を使った場合、局所的なスコープに閉じ込めて使う感覚が重要です。ただし、基本的には
let
で書けるところは
let
で書いたほうがSwiftっぽいスッキリとしたコードになるので、
let
を優先するのが一般的です。
変数としての
var
は意外と使えるかもしれませんが、
let
で書く観点からすると、外から来る値が影響するかもしれないという心配があるかもしれません。それでも、局所的な使い方に限定することで、より良いコードを書ける可能性があります。また場合によっては、
var
の使用も悪くない選択肢であるということです。 そこを気にせずに使うことと注意して使うことでは、その後のコードの品質が大きく変わってくると思います。ここでは、関数やアクションについて書いていきますね。
まず、関数の戻り値としてインターバルのオプショナル型、または文字列のオプショナル型を受け取るということで始めます。例えば、次のように書きます。
func exampleFunction() -> String? { // ここでインターバルやストリングを処理する }
ここで、
if let value = ...
を使って関数の結果を処理します。これは特に問題ないですね。例えば次のようにします。
if let value = exampleFunction() { print(value) }
一方で、
guard let
を使う方がより安全な場合もあります。さて、ここで何かしらのアクションに対して値を渡す場合を考えます。変数
str
を作成し、それに "abcde" を代入して、これをオプショナルとして扱います。
var str: String? = "abcde"
しかし、これだけではあまり意味がないので、何か気の利いたことを考えたいですね。ここまでのコードは正しく動作しますが、警告が出ないのが気になります。もしかしたら余計なエラーが邪魔しているのかもしれません。
Xcodeでプレイグラウンドを動かすと、タイミングがずれることがあります。そのため、不要なエラーを取り除く必要があります。
しかし、今回はエラーが出ないので、そのまま進めます。変数
str
を使用して何かを行います。例えば、変数
value
の内容を変更します。ここでは
mutating
系メソッドを使用します。
if var strValue = str { strValue.append("xyz") print(strValue) // ここでは "abcdexyz" が出力されます } else { print("Value is nil") }
let
キーワードを使用すると当然ながらエラーになります。
let
は不変であり、
var
は可変です。
プログラムの構造に影響を与えるので、このような使い方には注意が必要です。変数に対して文字列 "xyz" を追加しているので、出力される内容も変わります。誤解を避けるために、慎重に使う必要があります。
つまり、適切に使用できる場面を考える必要があります。たとえば、キャプチャリストなどの文脈では役に立つかもしれませんが、一概には言えないですね。注意して使いましょう。 Swiftのプログラミングにおける注意点について話しましょう。特に、変数や定数の取り扱い方、そして誤解を招かないコードの書き方についてです。プログラミングでは、読みやすさや明確さが重要です。チームでコードを書いている場合、特にその重要性が増します。将来的にコードを見直すとき、自分自身で誤解しないようにするためにも、誤解を招かない表現を使うことが大切です。
例えば、値を短縮形で変数に代入する場合でも、その方法に気をつける必要があります。ショートハンドの使い方によっては、シャドウイング(変数の再定義)が発生して誤解を招くことがあるからです。
if let
ショートハンドを使うときも注意が必要です。
if let
によるシャドウイングを避けるためには、変数を明示的に記述してあげることが一つの方法です。例えば、
if let unwrappedValue = value
のように記述すると、アンラップされた値が
unwrappedValue
という新しい変数に格納されます。
具体例を考えてみましょう。以下のコードのようにシャドウイングを避ける書き方があります。
if let unwrappedValue = value { // unwrappedValueを使用する } else { // valueがnilの場合の処理 }
これにより、アンラップされた値が他のスコープと衝突することを防げます。
また、ガード文を使う場合には、アンラップされた値を新しい変数に代入することで、コードの明快さを維持することができます。例えば、次のような記述が考えられます。
guard let unwrappedValue = value else { // 処理を終了する return }
こうすることで、
unwrappedValue
はそのスコープ内で安全に使用することができますし、後続のコードも分かりやすくなります。
クロージャーを返すメソッドの場合でも、同様のアプローチが取れます。以下に例を示します。
func makeDuplicateClosure(value: String?) -> (() -> String?) { guard let unwrappedValue = value else { return { return nil } } return { var mutableValue = unwrappedValue mutableValue += " (duplicated)" return mutableValue } }
このようにして、クロージャー内でもシャドウイングを避けつつ、明確なコードを書くことができます。
以上のように、計算や処理の中で使う変数の取り扱いは慎重に行い、誤解を避けるために必要な工夫をすることが大切です。これは特にチームで開発を進める場合に大きなメリットとなります。 とりあえず試してみた感じだと、このケースは少し強引すぎるかもしれませんが、確かに可能ではあります。このときに
if
guard
を適用しないと7行目が1行余計になります。確かにこの7行目を省略すると良いのかもしれませんが、7行目自体も何をやっているのか分からなくなります。3行目も同様に少し何をしているのか分からない感じがしますが、どうせなら3行目にまとめてしまったほうが良さそうです。これ両方あっても、片方でも複雑さはそんなに変わらないのなら、コード量が少ない方がわかりやすくなるはずです。
それで、ショートハンド(省略記法)であればシャドーイング(変数の再定義)もしている場合がありますね。以前に話した通り、
guard
を使う場合はシャドーイングをしない方が良いのではないかという意見もありますが、ショートハンドの場合は少し雰囲気が異なってきます。今回の例で見る限り、シャドーイングをあえてしなくても理解しやすい気がします。なぜこのような差が出るのかは分かりませんが、この場合は変数を再定義せずに、そのまま使えば良いという感じがします。
それでは、例えば
guard
で取得した値をインアウトパラメータとして渡したいとします。Swiftのこの中にテストコードとして入れてみたと仮定します。その場合、このコードの14行目でエラーハンドリングを確認しているように見えます。21行目だと渡せるようですが、テストコードとして使うのであれば意味があるかどうかは分かりません。ただ、インアウトパラメータとして渡したい条件があるなら使用されるかもしれません。
また、
guard
で取った値をインアウトで確保する場合、戻り値としてパラメータを書き換えることを想定しています。パフォーマンス面を重視する場合、インアウトを使う発想もあります。巨大な配列を渡す場合なども考えられます。ただ、変数を宣言してそこに代入する動きはパラメータに渡すのと変わらないはずです。構造体の場合のメモリ確保の仕方なども含めて基本的には変わらないでしょう。
この場合で、ガードレットやガードバーでメモリの確保の仕方が変わるかどうかは基本的には変わらないはずです。それでもコピーしている場合もあるでしょう。 おそらくここでは、データコピーの効率やSwiftにおけるメモリ管理の話がされていますね。
まず、配列に対する操作を取り上げてみます。
if let
if var
といった条件文を使用する際に、値がコピーされるかどうかが違いとして議論されています。例えば、
if let values = values
とした場合でも、実は値がコピーされていることが考えられます。同様に、
if var
でも同様にコピーされるとされており、これを実際に確認する手法も試せるかもしれません。
次に、配列や文字列操作において、値コピーと参照コピーの違いが強調されています。特に、実体を1つ持つだけで、ポインタが増える場合や、初めて変更があったときにコピーされる動きなどがポイントです。Swiftでは、コピーオンライト(Copy-On-Write、COW)が内包されていますので、効率的に動作することを目指しています。
また、
if let
if var
でのコピーの挙動や効率の違いも議論されました。たとえば、
if let
ではコピーが遅くなる可能性があり、一方、
if var
を使うと編集が生じたときにメモリが効率的でない場合があるかもしれません。この点で、注意が必要です。
話は進んで、
Reduce
メソッドについて触れられました。具体的には、次のようなコード例が示されています。
let sum1 = values.reduce(0) { (currentValue, nextValue) in return currentValue + nextValue } let sum2 = values.reduce(into: 0) { (currentValue, nextValue) in currentValue += nextValue }
ここでは、
reduce
は初期値を使いながら順次計算を行っていく一方で、
reduce(into:)
はクロージャに渡された値を直接インアウトで操作するという違いが説明されています。
計算の効率性やメモリ使用の最適化についても言及しました。
Reduce
の標準の使い方では、毎回新しい値を生成して返すため、メモリの使用が多くなる可能性があります。一方、
reduce(into:)
では、一度生成した変数に対して、繰り返し操作を行うため、より効率的とされています。
これらの違いを理解して適切に使い分けることが、プログラムのパフォーマンス向上に繋がります。最後に、
reduce(into:)
の最適化が図られれば、メモリ操作が効率良くなることも強調されています。
総じて、Swiftのメモリ管理やデータ操作の効率性を理解するために重要なポイントが示されています。この知識を基にコードを書いていくことで、より効果的でパフォーマンスの高いソフトウェア開発が可能になるでしょう。 要は、このコードを修正しましょう。具体的に書くと、まずは関数
add
を定義します。この関数は
parentValue
という整数を受け取り、整数を返します。
return
を使って
nextValue
を足し算する形です。これを要素がなくなるまで繰り返すと良いでしょう。
最初は混乱するかもしれませんが、一生懸命読むことで理解できるようになります。まず、
add
に対して条件分岐が必要です。具体的には
if
switch
で条件を作成します。
func
Int
の配列を使い、以下のように書けます。
if let
を使って
values.first
が存在する場合をチェックし、
return
文を使って計算結果を返します。
func add(values: [Int]) -> Int { if let firstValue = values.first { let remainingValues = Array(values.dropFirst()) return firstValue + add(values: remainingValues) } else { return 0 } }
このように書いてみましたが、自信がないので後で確認してみます。必要があれば訂正しますが、このコードでは、
var result = 0
としてループで各要素を足し合わせる方法もあります。
func add(values: [Int]) -> Int { var result = 0 for value in values { result += value } return result }
一度定義したのを再定義するのは同じ感じになりますが、
inout
キーワードを使ったほうがパフォーマンスが上がる場合もあります。
このように実装です。
inout
を使うことで、コピーイン・コピーアウトの原則に従いつつ、メモリを最適化します。例えば、
Accumulator
var
で宣言し、直接書き込んでいく最適化の恩恵を受けることができます。
一方で、
inout
を使わずに実装した場合は、リターン時に一時的にメモリを確保する必要があります。これによって最適化の観点からは劣る場合があります。参照側と異なる話になりますが、このようなメモリ管理の流れを理解すると良いでしょう。再度確認が必要な場合もありますが、この基本の流れで理解を深めることができます。 前回の勉強会で取り扱った内容について話していきます。アキュムレータについて話題に上がりましたが、前回の内容は特にバー
(var)
に関連していました。イニシャルなリザルトが
var
アキュムレータに代入されるとき、不変値(定数)は開放され、新しい不変値が代入されます。これは同じメモリー空間を使い回すため、リターンするまではメモリーが開放されないということです。ただし、リターンをしたらメモリーは開放されます。
ポインタに関してですが、スマートポインタのように、バーの場合ポインタ自体が実体を指す形になります。この場合、記憶は変わらないため、ポインタは変わらずに済みます。そのため、再度アロケートする必要はなく、アロケートしたメモリーをそのまま使って内容を書き換えていく形になります。これにより、メモリー上のデータが直接変更されるイメージです。
メモリーの管理や操作に関しては、C言語のように
memcpy
関数でアキュムレータをコピーすることに似ています。異なる点としては、値を代入する際にポインタの指す先を変更するか、データ自体を書き換えるかの違いがあります。インラインでのコピーコストも同様に存在することを考慮する必要があります。
コピーコストの話をすると、
inout
を使った場合でもコピーコストは発生します。ただし、最適化が行われることで、純粋な参照に変わる点が異なります。それがどう効率に影響するかはケースバイケースになりますね。
使用する際の利便性についても触れられました。クラス型の場合、インスタンスを新たに作る際の負荷が大きくなるため、
var
を使ってヒープにメモリを確保し、値を次々に書き換えていく方法が有効です。値型の場合は、リターンのたびに新しいインスタンスを作成することを避けられないため、参照型とは異なるアプローチが必要になります。
最終的に、リダクションする際の考え方や参照と値の使い方の違いなど、それぞれの選択基準について理解が深まりました。今日お話した内容は複雑な部分もあるので、また次回にでもさらに深掘りしていきましょう。時間になりましたので、本日の勉強会はここまでとします。お疲れ様でした。ありがとうございました。