引き続き The Basics
における オプショナル
の最後の項、自動で強制アンラップされるオプショナル
について眺めていきます。前回はその導入的な基礎的なところを確認したので、今回はその表現のしかたや特色みたいなところを確認していこうと思ってます。よろしくお願いしますね。
今回もゆめみ社外に向けた参加者公募がされまして、ゆめみの外の人も幾名か来られての開催になります。
——————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #173
00:00 開始 00:10 前回のおさらい 00:51 暗黙アンラップなオプショナルの特徴 03:05 オプショナル型の記載についての再確認 05:00 糖衣構文をもつものの表記のしかたについて 06:55 配列を強調したいときには良いかも? 07:38 インスタンスを生成する際の印象の違い 08:24 変数宣言時の印象の違い 08:38 初期化とリテラル変換 09:45 中間言語レベルで違ってくる? 10:32 SIL と中間コードの恩恵 12:46 Swift 中間言語で見比べてみる 13:20 中間言語の出力 14:58 Raw SIL を出力してみる 15:57 正規化 SIL も出力してみる 16:27 Raw SIL 同士で比べてみる 19:16 正規化 SIL 同士で比べてみる 20:10 それほど気にするようなものでもなさそう 21:40 糖衣構文に慣れていくのが無難かもしれない 22:11 糖衣構文に対して extension 可能 24:39 型エイリアスに対しても extension 可能 25:46 新しい型拡張についての所感 27:28 暗黙アンラップされるオプショナルの利点 27:45 値が決まってから再び nil にならないときに便利 28:48 クラスの初期化でよく使われるらしい 29:05 無所有参照については今後に扱う予定 29:38 値が保証されれば便利に使える 31:15 暗黙アンラップなオプショナルの所感と次回の展望 ———————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #173
では、始めていきますね。今日も引き続き、オプショナルについてのお話になります。暗黙的にアンラップされるオプショナルについて、前回基本的なところをご紹介しましたね。
前回は、「びっくりマーク(!
)」をつけて表現する方法をお話ししました。これを使うと、アクセスするたびにオプショナルの値を検査してアンラップする必要がなくなるので、状況によっては非常に便利です。
では、おさらいとして例を挙げます。オプショナルの値があるとき、例えば普通のオプショナルだとしますね。これを使うときに、そのままプリントしようとすると、警告が出ます。オプショナルの値だけどどうするのか、という感じにプリントの場合はなります。よりわかりやすい例として、Int
を取る関数を使うと、オプショナルの値を渡そうとするとコンパイルエラーになります。これをどう処理するかというのは、いろいろ検討が必要です。
基本的には、値が最初は未定な場合もありますが、途中で値が確定してそれをずっと使える状態にすることもあります。このような場合には、いちいち強制アンラップや二項演算子などを使わずに済む方法として、びっくりマーク(!
)で強制アンラップを利用します。これにより、オプショナルの値が確実に存在するとわかっている場合には、強制アンラップの記述を省略できます。
次に、このオプショナルの書き方ですが、以前もお話ししたように、Int?
と Optional<Int>
は同じ意味です。ただ、通常はInt?
の書き方が一般的に使われますね。これも同じ内容で、表記が簡略化されているだけです。
配列にも似たような感覚がありますよね。例えば、[Int]
と Array<Int>
も同じ意味です。どちらも間違いではありませんが、通常は [Int]
の方が多く使われると思います。
ただし、あまり慣れていないと [Int]
よりも Array<Int>
の方がわかりやすいと感じることもあります。これは慣れの問題で、どちらも正しい書き方です。個人的に [Int]
の方が見やすいと感じる場合もあれば、Array<Int>
の方がわかりやすいと感じる場合もあるでしょう。
以上、おさらいと補足を含めたお話でしたが、次に進む前に一度休憩を取りましょう。その後、さらに詳細なオプショナルの使い方や、特徴的な表現方法について見ていきたいと思います。もし質問があれば、このタイミングでどうぞ。 例えば13行目を選んだりすることもありますね。遠距離の時も使ったりしますけど、注釈を入れておけばわかりやすくなりますよね。これでもいいし、それも同じことです。
内部的には同じように動作するのですが、若干違いがあります。上の2つはイニシャライザーが走り、下の1つはリテラル変換が走るという違いがあります。ただ、その違いは大したことではありません。細かく言うと違いますが、表向きから見たらまったく一緒です。
確かに最適化がかかるとまた同じになる可能性もないとは言えません。イニシャライザーとリテラル変換を同一視してくれるかどうかは、独自の型を作った場合にはそれはないと思います。しかし、配列の場合は特別な内部的な処理が行われる可能性もあります。標準ライブラリーなので、特別扱いがされる可能性もありますね。
中間言語にしてみれば、その違いがわかるかもしれません。やってみるのも面白そうですね。ただ、Swiftを普通に使うユーザーにはあまり気にしなくていい部分かもしれません。スイフトインターメディエイトランゲージ(SIL)というのがありまして、Swiftの中間言語です。
Swiftはコンパイルするときにいろいろな加工を行います。一般的に、Swiftコードはまず中間言語にコンパイルされ、その後バイナリーコードに変換されて実行されるという流れになります。この中間言語を作ることによって、一旦中間言語を最適化して、より洗練されたコードにするという段階があります。
プログラムを構築する際に、特定のデータを変換して自分のアプリで使いたい場合、例えばXMLファイルを読み込んでアプリ内で表示したい場合などがあります。XMLを直接扱うとコードが複雑になることがありますが、一旦XMLをCSVに変換し、さらにCSVから自分のデータに変換するという手法を取ることで、コードがスッキリし、保守しやすくなります。また、制御もしやすくなります。
このように、一時的に別のフォーマットに変換する手法を取ると、思わぬ効率化が図れることもあります。データ処理の際には一度別の形に変換する方法を検討してみることをおすすめします。
さて、このような考えは、Swiftの内部的な処理にも当てはまります。中間言語を利用することで、コードの最適化や実行効率の向上を図ることができるのです。ただし、中間言語のコードはやや読みにくくなることもありますが、ステップを踏むことで、最終的には最適化された良いコードを得ることができます。 アレイのSwiftについて、まずコードを書いていくことになります。Swiftで配列を扱うにあたって、レッドバリューが重要になりますね。まず最初に、let
で値を設定します。たとえば、let value = 100
のように設定します。
ここで、中間言語(Intermediate Language、略してIL)に出力することを考えます。Swiftのコマンドで、中間言語やダンプのコマンドを利用しますが、別にXcodeを使っても構いません。環境が整っていないと面倒ですが、まずはアップデートが必要かどうか確認しましょう。
スムーズに進めるためには、環境設定が必要です。デベロッパーツールがインストールされていないと、Swiftのコマンドが動作しないことがあります。Xcodeを選択するときはxcode-select
を使って正しく設定しましょう。
Swiftコンパイラを使って、ILの出力ができます。例えば、swiftc -emit-silgen
やswiftc -emit-ir
などのコマンドを使えば、中間言語の異なる形式を出力できます。それぞれの形式を使えば、最適化前と後のコードを見ることができるので、コードの理解が深まります。
具体的には、アレイの初期化コードをSwiftで書いた後に、シルジェンやローシルなどを使って比較してみます。
以下のようにSwiftコードを書きます。
let array = [1, 2, 3]
このコードをswiftc -emit-silgen
で中間言語を出力すると、シンプルな形式の中間言語が得られます。同様にswiftc -emit-sil
でキャノニカル(最適化された)形式の中間言語を得られます。これらを比較することで、最適化の違いやコードの生成方法を学ぶことができます。
また、コードの比較にはエディタの機能や、diff
コマンドなどを使うと便利です。フォントのサイズや表示形式によって見やすさが変わるので、自分が見やすい設定にすることをお勧めします。
以上のように、Swiftの基礎的な部分を理解しながら、中間言語の出力やコードの最適化を学習していくと、より深く言語の仕様や動作を理解することができます。 それでは、この部分についてもう少し整理していきます。
まず、前提として、関数の中で初期化を行う部分があったと思います。イニシャライザー
という言葉が出てきましたが、これはオブジェクト指向プログラミングにおけるコンストラクタを指しているものと思われます。
次に、「代入しないとだめ」という話がありましたね。初期化処理では、オブジェクトが生成された直後に何らかの値を代入することが必要だとされています。初期化していない変数やオブジェクトを使用すると、未定義の動作を引き起こす可能性がありますので注意が必要です。ここでの初期化に関しては、デフォルトのイニシャライザーが使用され、その結果が「パーセントロック」にストアされているようです。そして、これがさらに変数パーセント3
に格納されていると説明されました。この部分は以下のような形で表現できるかもしれません。
let defaultValue = SomeType() // デフォルトのイニシャライザー呼び出し
let percentLock = defaultValue
あとは、パーセント3
というのがInt
型のメモリー領域を示しているとのことです。これはグローバルなアドレスか何かを参照しているようですが、おそらくプログラム内部での具体的なメモリー管理に関する話だと思われます。
次に、この部分について「この辺に用意されている場合」が「global」や「hidden」なアドレスであること。これはSwiftのメタタイプに関連する用語かもしれません。
また、リテラルから値を初期化する場合についても言及がありました。Swiftではリテラルから直接初期化することもできます。例えば:
let someValue = 42 // リテラルから初期化
この部分については、ストラクトやローポインターなどの用語が登場して、少し複雑な処理が行われているようです。
さらに、14行目のコードについては、読みやすさや解釈間違えない書き方だったと言及されています。法位公文(HOF: Higher Order Functions)についてもコメントがありました。型推論や代入先省略といったSwiftの機能を有効に活用することで、よりシンプルなコードを書くことができるようです。
例えば、以下のような書き方が推奨されます:
let inferredType = 100 // 型推論による省略
このように、Swiftではオプショナル型や法位公文など強力な機能がありますが、どの機能を使うかはケースバイケースです。自分のコードの理解しやすさや保守性を考えつつ、適切な場所で適切な機能を使うことが大切です。 そこで次の話なのですが、この法位公文ってエクステンションで使えるのかな?使えるはずがないか。でも、エクステンションとインターフェイスについてはどうなのか、確認します。あれ、使えたね。最近、新しいSwiftのバージョンで拡張された機能があるから、これは完全に法位公文ですね。
具体例として、Int
に対してfunction sum
を作って、それを連結して使用できますね。でもこれはsum
なんかと混同しがちなので、getValue
という名前にして、Int
に変換して、最終的にはforce unwrap
かunsafe unwrap
で返すことができます。これだと、オプショナルのInt
値でも使えます。また、Double
のオプショナルに対しても同様にgetValue
は使えないでしょう。
以前のSwiftのバージョンでの書き方だった場合、オプショナルのunwrap
がInt
の時はどうするか、というような書き方が必要でした。しかし、最近のSwiftではオプショナル型に対してもそのままジェネリック型として記述できるようになったんです。これが拡張された法位公文の一例です。
また、先日話題に上ったタイプエイリアスの相互作用についてですが、具体的にはtypealias Id = Int?
のように宣言すると、Id
型としてInt
のオプショナルが使用可能になります。そして、タイプエイリアスをそのままエクステンションすることもできるようになりました。この点もまた便利です。エクステンションに対して直接ジェネリック型を適用する書き方ができるようになったことで、拡張するコードがずっと簡潔でわかりやすくなりました。
ただし、覚えておきたいのは、例えばId
型に対してアクションを追加しても、それは元のInt
のオプショナル型にも影響するということです。この点だけは注意が必要です。
新しい言語仕様のおかげで、例えば15行目にあるコードが23行目にも使えるようになったというのは非常に便利です。これでコードがもっと理解しやすくなるので、是非覚えておくと良いでしょう。 前回のお話のおさらいはこのくらいにして、表現の部分を見ていくことにしましょう。残り時間はあと5分しかありませんが、もし時間が足りなかったらまた次回に回しますね。では、次に進みます。
今回は、暗黙的にアンラップされるオプショナルの利点についてお話しします。間に話が出たため、思い出すのは大変かもしれませんが、ビックリマーク (!
) を使うことで宣言時に強制アンラップをしなくても値が取り出せるという利点があります。
オプショナルが定義された後すぐにその値が存在することが確認され、その後も常に値が存在することが想定されるときに便利です。この便利さについては、前回も IBOutlet
の例を挙げてお話しましたね。初期化の時点では値が決まらないけれど、その後すぐに値が決まり、使う前には確実に値があるという場合です。
このような状況でわざわざビックリマークを使わなくてもよいので便利です。ただ、この程度の便利さであれば「ビックリマークを使えばいいだけじゃないか」と感じるかもしれません。しかし、他にも考え方があると思うので、それについてはまたおいおい話していきます。
暗黙的にアンラップされるオプショナルは便利です。クラスの初期化の中でよく使われます。ここで注釈がありますが、unowned
のリファレンス(無主参照)と強制アンラップのプロパティで説明されている通り、循環参照とクラスの相互参照の時に便利です。これも興味深い話ですので、オプショナルの強制アンラップについて一通り見た後で触れていきたいと思います。
具体的な例として、以下のような構造の場合を考えます。
var value: Int!
この時点では値が決まっていませんので、nil
を入れておくか、省略しても問題ありません。その後、値が決まった時点で代入し、以降のコードでは値が必ず存在することが約束されています。そのため、毎回強制アンラップしなくても、自動でアンラップされるようにしておくと便利です。
例えば、ウインドウの初期化などで、
@IBOutlet weak var myWindow: NSWindow!
のようにしておくと、初期化後すぐに値が決まるので、以降のコードで強制アンラップする手間が省けます。
関数やその他の例でも同様で、自分が制御できないコードが増えると良くないので、制御できる限りはしっかりと制御することが大切です。
少し話が長くなりそうなので、また次回に詳しくお話しします。今日は時間になりましたので、これで勉強会を終わりにしましょう。
お疲れ様でした。ありがとうございました。