Page icon

第54回

2021/12/06
A Swift Tour
Empty
Empty
Empty
Empty
12 more properties
今回は再び 
A Swift Tour
 に戻って続きから眺めてきます。最初に 
オプショナルチェイニング
 についてさらっと確認して、そうしてクラスの項を読み終えた後は、いよいよ 
列挙型と構造体
 の項へと移っていくことになりそうです。どうぞよろしくお願いしますね。
——————————————————————————— 熊谷さんのやさしい Swift 勉強会 #54
00:00 開始 00:57 オプショナルチェイニング 03:40 添字構文でも利用可能 04:45 オプショナルチェイニングの連続利用 08:04 Swift Fiddle 09:02 オプショナルチェイニングを使ってみる 09:36 浮動小数点数 12:18 オプショナルで扱うとき 16:54 オプショナルチェイニングがなかったとすると 20:21 Boolean への暗黙変換 23:45 BooleanType 27:36 質疑応答 28:36 列挙型と構造体 28:59 値型 30:56 列挙型の定義 31:56 列挙型の特徴 33:21 Raw 型と Raw 値 34:33 最低限のデータサイズで構成 36:01 Raw 値を関連づける 36:43 列挙子の書式 39:04 Raw 値を明示する 39:59 RawRepresentable 42:11 列挙型のデータサイズ 43:20 Raw 値の評価タイミング 46:46 Raw 値の影響範囲 47:39 Raw 値によるインスタンス化 49:35 クロージング ———————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #54
はい、では今日は、前回ではなく前々回の続きで、「Objects and Classes」セクションの最後の項目であるオプショナルチェーニングについて話していきます。このセクションはクラスの話ですが、オプショナルチェーニングは全体通して使えるので、そのように理解してください。
まず、オプショナルチェーニングは、Swiftの標準機能であるオプショナル型、つまりヌル安全のための型を便利に使うための仕組みの一つです。具体的には、オプショナル型があったとき、そのインスタンスに対して
?
を付けることによって、インスタンスが空でなければその後ろの操作を実行し、空であればそこで処理を打ち切って
nil
とするというものです。
これは従来のオブジェクト指向言語とは異なり、安全で便利な機能となっています。従来のC++やJavaなどでは、オブジェクトが
nil
になる状態が普通にあり得るため、プログラムの流れで「うっかりインスタンスが
nil
だった」ということが起こり得ます。しかし、Swiftはオプショナル型を導入することにより、クラスのインスタンスは純粋には
nil
を含まず、オプショナル型と併用することで初めて
nil
を許容する形になっています。そして、オプショナルチェーニングを使うことで、例えば以下のように、オプショナルのインスタンスが
nil
でなければサイドレングスを取り、
nil
であれば
nil
とする動きを1行で書けるのが大きなポイントです。
let sideLength = optionalSquare?.sideLength
このような記号をオプショナルチェーニングと言います。スライドに書かれていることは重要なポイントとして、メソッド呼び出しやプロパティアクセスだけでなく、ストレージ構文やサブスクリプトにも
?
が使えることがあります。そして、インスタンスが
nil
のときには操作が無視されて式全体が
nil
になることも重要な点です。全体として操作が無視され
nil
になると押さえておけば問題ありません。
また、オプショナルチェーニングを連続で書くこともできます。この場合、途中で
nil
が現れない限り以降の評価式がすべて動作しますが、ある段階で
nil
だったときには操作が無視され、全体が
nil
になります。このような場合、最終的な戻り値は最終目的の型をオプショナルで包んだ型になります。
これらの概念を理解するために、プレイグラウンドで実際にコードを動かしてみると分かりやすいでしょう。しかし、最近プレイグラウンドが動かないことがあり、これは再起動やMacの再起動でも解決しない場合もあります。今、Zoomのコメントで教えてもらった別のプレイグラウンドツールを試してみましょうか。
では、具体例を使いながら見ていきましょう。不動小数点数のダブル型を使って説明してみます。例えば、以下のようにダブル型の値を持つオプショナルを使う場合です。
var someValue: Double? = 42.0
ちょっと待ってください、プレイグラウンドが動かないですね。インポートが足りないかもしれません、
import Foundation
を試してみましょう。リロードしてもダメな場合もあるので、これで様子を見てみます。
あ、ランがつきましたね。バグを呼び起こしたような気分ですけども。続けてダブル型の値を使いながら説明していきましょう。 ダブル型を定義して、プレイグラウンドをちゃんとプリントしないといけないのかという話ですね。プレイグラウンドでの実行時間を考えると、ターミナルでいいんじゃないかという意見もありました。実際、ターミナルで実行するのも一つの方法です。
今日の話の中で Swift Playgrounds という別のサイトについて言及がありました。Swift Playgrounds はもともと iPad 向けに出ているものですが、現在では Mac 用のバージョンも出ています。これも良い選択肢かもしれませんが、今日はとりあえず話しながら進めていきます。
不動小数点数型にはさまざまなプロパティがあり、これを理解することで面白くなります。あまり使う機会はないかもしれませんが、例えば
Double
型には多くのプロパティがあります。エクスポネント(累乗)とか、無限大や有限かどうかを調べるプロパティです。非正規数を調べるプロパティなど、いろいろあります。
例えば、以下のようにダブル型を定義し、その中でプロパティを使用します。
let value: Double = 10.5 let exponent = value.exponent // 累乗 let isInfinity = value.isInfinite // 無限大かどうか let isFinite = value.isFinite // 有限かどうか let significand = value.significand let ulp = value.ulp // 現在値の次に取れる値
上記のプロパティを使って、実際の値を構成することができます。例えば、
significand
exponent
を使って値を計算すると以下のようになります。
let x = value.significand * pow(2.0, Double(value.exponent)) print(value) // 10.5 print(x) // 10.5
オプショナル型に関しても触れました。仮にある値がオプショナル型として用意されている場合、オプショナルチェーニングを使ってビットパターンを取得する方法もあります。
let y: Double? = 10.5 if let bitPattern = y?.bitPattern { print(bitPattern) // ビットパターンが表示される }
この場合、ビットパターンは
Int
型で返ってきますが、文字列に変換する方法もあります。
if let bitPattern = y?.bitPattern { let bitPatternStr = String(bitPattern) print(bitPatternStr) // ビットパターンを文字列として表示 }
オプショナル型のプロパティチェーニングを使うことで、さらに詳細な値を取得することも可能です。例えば、
significand
ulp
など、通常のプロパティでもオプショナルに対してチェーニングできます。
if let significand = y?.significand { print(significand) // シグニフィカントが表示される } if let ulp = y?.ulp { print(ulp) // ULPが表示される }
全体的に、Swift の不動小数点数型を操作するためのプロパティやオプショナル型のチェーニング方法についての理解が深まりました。今回は特にダブル型のプロパティの活用とオプショナルチェーニングについて重点的にお話ししました。 確かにそうですね。だからこれはオプショナルチェーンを連続したとは言わないですね。オプショナルを返すメソッドを実行したときの話です。うんうん、はい。何にしても、オプショナルチェーニングの素晴らしいところは、
if
文を書かなくても
nil
を考慮した安全なコードが書けるという点です。
例えば、オプショナルチェーニングがあることによって、次のように書けます。
let z = y?.magnitude
しかし、オプショナルチェーニングを使わない場合はこんな感じになります。
if let y = y { z = y.magnitude } else { z = nil }
オプショナルチェーニングを使うことで、上記のような17行目から26行目までのコードが1行で書けるのです。これは非常に画期的な機能です。昔はこのようなコードを書かなければならなかったので常識でしたが、オプショナルチェーニングのおかげで安全かつシンプルに書けるようになりました。
Objective-Cを触ったことがある人にとっては、
nil
だったら無視して突き進む仕様に非常に驚いたと思います。この仕様をうまく取り込んで、現代風にアレンジしたのが今のSwiftということができます。
例えば、
let z = y?.magnitude
といったコードを書くことで、Objective-Cの
NSNull
などのオブジェクト型でなければ
nil
を取れないという制約をうまく回避しています。
また、Objective-Cでの
nil
判定は次のように書くことができます。
if (y != nil) { z = y.magnitude; } else { z = nil; }
このように、Swiftでは
y
nil
かどうかを簡潔に判定できるのです。
ちなみに、C++でも
if(y)
という書き方ができますが、最近のC++11以降の仕様では
nullptr
という型が追加され、
0
を表す
NULL
から、
nullptr
が登場しました。これにより、正確に
NULL
判定ができるようになったため、
if(y)
といった書き方は推奨されなくなってきています。
C++ではクラスに暗黙型キャストを定義することができ、例えば、あるクラスがあった場合、
operator bool
のようなコードを定義すると、
bool
キャストしたときに特定の処理を行うことができます。これにより、
y
があるクラスだった場合、暗黙の
bool
キャストが働いてしまう可能性があるので、明示的に
NULL
判定をしましょうという話があります。この話を聞いて、Swiftの
if
文の条件を強制的に
Bool
型にする仕様は素晴らしいと感じました。
最後に、昔のSwiftには
BooleanType
というプロトコルがあり、それに準拠させると
if
文にそのインスタンスを使えるという仕様がありましたが、現在は廃止されています。 なので、これがあった頃は、
Extension
Double
型を
Boolean
タイプとして指定して、たとえば「
self != 0
」のようなコードを書いてしまうと、
Double
型をそのまま
if
文に渡して判定できるという仕様がありました。確か、これはパフォーマンスなどの都合で廃止されたはずですが、個人的にはとても気に入っていました。
ただ、今になってみると、インスタンスそのまま渡すのは少し気持ち悪いかなとも思います。例えば、
DarwinBoolean
から
Bool
型へ変換する必要が出てきた際、
Bool
値を明示的に取得してSwiftの
Bool
型に実質的に変換しないといけない仕様になっています。こういった厳密な仕様にはなっていますが、
Bool
を意味する型が
if
文でそのまま使えないというのも違和感があると言えばあります。しかし、今はこのような流儀になっています。その部分については少し脱線してしまいましたが、頭の隅にでも入れておいてください。
では、次の話題に移ります。オプショナルチェーニングについてです。オプショナルチェーニングはとても便利な機能で、多くの人が使用しています。
?
を使って連続して呼び出すことで、オプショナル型の安全なアンラップを行えます。
何か質問がありますか?大丈夫そうですね。それでは次に進みましょう。続いての項目は列挙型と構造体についてです。個人的な感覚では、Swiftの主役とも言えるデータ型かなと思います。大きく分けると、値型と参照型がありますが、Swiftは特に値型と相性が良い言語だと感じます。
前回はオブジェクトとクラスの話をしましたが、今回は列挙型と構造体という異なるタイプについて説明します。列挙型は一定の有限の範囲から特定の値を表現する型で、構造体は複数のデータを一つにまとめる型です。個人的には、列挙型は横に並列に並んでいる印象があり、構造体は縦に直列に並んでいるような印象を持っています。
では、具体的に見ていきましょう。まず、列挙型の定義方法ですが、
enum
というキーワードを使います。このキーワードは多くの言語で採用されていて、おなじみの宣言だと思います。
case
というキーワードを用いることで、この列挙型がどのような候補を取り得るかを列挙します。これが基本的な列挙型の定義方法です。
他の言語、特にC言語などでも列挙型がありますが、Swiftの列挙型はそれだけではなく、メソッドや計算型プロパティを持つことができます。さらにはプロトコルに準拠することもできます。これによって、単純に一定の範囲の中からどの値と選ぶだけでなく、さらに振る舞いを型に持たせることができる。この表現力の高さがSwiftの列挙型の魅力です。 このSwiftプログラミング言語に書かれていたサンプルコードですけど、これを見るだけでも色々な列挙型の特徴が詰まっているので、驚きながらこのコードを眺めていきましょう。まず、Swiftの列挙型では、各ケースがそれそのものでしかなく、値が関連づけられていない点が基本的なポイントです。そのため、データサイズが最小限に抑えられます。例えば、列挙型のケースが255個までなら1バイトで表現され、256個以上になると2バイトになります。C++やC言語の列挙型の場合、値が1つだけでも64ビット取られてしまいますが、Swiftの場合はそうではありません。値とケースが関連づけられていないため、メモリ効率が良くなります。
列挙型に値を持たせたい場合には、
rawValue
を設定してその型を関連づけることができます。
rawValue
Int
型だけでなく、
Double
型や
String
型なども使用できます。また、自作した型を使用することもできます。例えば、Swiftの列挙型はメソッドを持つことができますし、ケースをカンマ区切りで列記したり、別の行にそれぞれ記述したりするなど、表現の自由度があります。
次に、
rawValue
を使った例を見てみましょう。以下のように列挙型を定義します。
enum Rank: Int { case ace = 1 case two = 2 case three = 3 }
このように整理すると、各ケースに対して
rawValue
を持つようになり、その値が自動的に割り当てられます。例えば、
ace
rawValue
は1になります。また、逆変換も可能で、
Rank(rawValue: 3)
とすることで、
three
を取得できます。
rawValue
を持つ列挙型は自動的に
RawRepresentable
プロトコルに準拠します。そのため、
RawRepresentable
プロトコルに準拠したメソッドにも対応できます。
仮に
rawValue
を持たない列挙型にすると、
RawRepresentable
に準拠しないため、そのようなメソッドには対応できなくなります。
以上のように、Swiftの列挙型の特徴とその利点について述べました。列挙型のケースが値とは独立していること、そのためメモリ効率が良くなること、そして
rawValue
を持たせることでさらなる機能が追加されることなど、Swiftの列挙型の使い道と魅力について理解していただけたかと思います。 改めて説明すると、何も「Raw」型を持っていない場合、
RawRepresentable
プロトコルを持つ関数に渡せません。しかし、ここで「Raw」型を設定することによって、
RawRepresentable
を持つ関数に渡すことができるようになります。この特性を知っているとコードの書き方が広がるかもしれません。
基本的にはこれで終わりですが、時間もなくなってきたので、少し余談を話しましょう。
まず、メモリーレイアウトに関してですが、例えば
enum
のサイズについて確認しておきます。ケースが一つしかない場合、そのサイズは 1 バイトです。
print
しないと具体的な値がわかりませんが、ケースが 255 個を超えると 2 バイトになります。これを見つけたときは面白いと思いましたが、あまり実用的ではありませんね。
次に、
RawValue
の関連付けについて少し話しましょう。例えば、
enum Rank: Int
があったときに、
Rank.ace = 1
と設定したとします。このとき、ランタイム上では
Rank.ace
1
ではなく、
Rank.ace
として扱われます。
Int8
型でキャストすると内部のデータがどうなっているかがわかります。例えば、以下のようにキャストします。
let value: Int8 = element as! Int8
すると、
が取れます。これは、ケースが1つしかない場合、サイズが 0 バイトになることが原因です。ケースが増えればサイズも増加し、
Int8
のサイズに一致します。
このように、
RawValue
として割り当てられた型が実際に値を持つのは
rawValue
プロパティを読んだときになります。このタイミングでインスタンス化され、例えば
Int
型や
Double
型、
String
型などのプリミティブ型が即時に生成されます。ただし、重たい処理が含まれる型の場合、このタイミングで注意が必要です。逆に
RawValue
にアクセスしない限り、列挙子と同じパフォーマンスで動作しますので、コードを書く上で参考にするとよいでしょう。
また、イニシャライザの話もしておきます。例えば、
RawValue
に基づくイニシャライザは次のようになります。
init?(rawValue: Int) { switch rawValue { case 1: self = .ace case 2: self = .two default: return nil } }
このように
RawValue
を使うことで、特定のケースに直接対応させることができます。
今日の内容は雑多な話題が多く含まれていましたが、まとめとしてはこんな感じです。これで今日の勉強会を終わりにしましょう。お疲れ様でした。ありがとうございました。