アンラップ記事の第3弾(になってしまいました)。
- 第1弾:
!
(強制アンラップ)の正体はどこに…? - 第2弾: 君は
!
より怖い強制アンラップを知っているか?
はじめに
Swiftでは避けて通れないOptional
型。値があるかもしれないし、無い(nil
)かもしれない。
値があればその値についての何らかの処理、無ければ無かったなりの処理、と分岐することはSwiftプログラミングにおいて何度も登場することかと思います。
では、その分岐させるためのコードの書き方をいくつ知っていますか?
この記事では筆者が思いつく限りのパターンを挙げていきます(実用性の有無は問わない)。他のパターンがあればコメントしていただければ適宜追加します。
問
以下のコードでmaybeInt
には、50%の確率でInt
型の何らかの値が入っています。残りの50%はnil
です。
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
maybeInt
がInt
型の場合はその値を、nil
の場合は"nilだよ。"
という文字列を表示するコードを書いてください。
解答集
!= nil
からの!
(強制アンラップ)パターン
一番分かりやすいのはnil
かどうかを!=
(または==
)演算子で比較してif
文で分岐するというやり方でしょう。他の言語から入ってきた"Swift初心者"風の書き方です。ただ、Swiftに慣れてくると後述のoptional bindingを使うことを覚えてそちらを使うようになるかと思います。
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if maybeInt != nil {
print(maybeInt!) // 強制アンラップを忘れずに
} else {
print("nilだよ。")
}
三項条件演算子
print
の一文で済むなら三項条件演算子を使う手もあります:
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
maybeInt != nil ? print(maybeInt!) : print("nilだよ。")
Optional Binding
Swiftにはoptional bindingという書き方があります。
if let constantName = someOptional { ... }
というように書くと、someOptional
に値がある場合、その値がconstantName
に代入されif
文の中身が実行されるというものです。
!= nil
に比べて(強制アンラップを使わずに)簡潔に書くことができるという利点があります。
if
文のoptional binding
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if let definitelyInt = maybeInt {
print(definitelyInt)
} else {
print("nilだよ。")
}
guard
文のoptional binding
ちなみにoptional bindingはguard
文でも使えます。その場合は、else { ... }
でearly exitが必要となる点に注意が必要です。
トップレベルでのguard
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
// トップレベルでearly exitをするために
// `exit`を使う
import CoreFoundation
// プラットフォームによってimport Darwinとimport Glibcを分ける書き方でも良い
guard let definitelyInt = maybeInt else {
print("nilだよ。")
exit(0)
}
print(definitelyInt)
クロージャを使ったguard
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
({ () -> Void in
guard let definitelyInt = maybeInt else {
print("nilだよ。")
return
}
print(definitelyInt)
})()
switch
文
nil
かどうかはswitch
文を使って判定することもできます。そのswitch
文を使った書き方も何通りかあるので見ていきましょう。
case let _?
を使って値を取り出す
個人的に?
を使ったこのswitch
-case
の書き方は当初なかなか慣れなかったのですが、こういう文法もあるのだと知っておくとどこかで役に立つかもしれません。
次のコードのようにcase
式でlet _?
と変数名の後に?
をつけるとnil
ではない場合のみに文が実行されます。
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
switch maybeInt {
case let definitelyInt?:
print(definitelyInt)
case nil: // `default`でも良い
print("nilだよ。")
}
Optional
がenum
であることを利用する
Optional<Wrapped>
はenum
として定義されており、case none
とcase some(Wrapped)
の二つのcase
から成ります1。.none
がnil
のことです。次のようにenum
であることを明示してswitch
文を書くこともできます。
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
switch maybeInt {
case .some(let definitelyInt):
print(definitelyInt)
case .none:
print("nilだよ。")
}
if
-case
実はcase
式はif
でも使えます。
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if case let definitelyInt? = maybeInt {
print(definitelyInt)
} else {
print("nilだよ。")
}
もちろんenum
として扱うこともできます
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if case .some(let definitelyInt) = maybeInt {
print(definitelyInt)
} else {
print("nilだよ。")
}
ちなみに、次のような書き方もできますが、Swiftコンパイラにバグ(SR-13904)があるため間違ったwarningが表示されます。実行に問題はありません。
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if case let definitelyInt as Int = maybeInt {
print(definitelyInt)
} else {
print("nilだよ。")
}
番外編
map
+ ??
アンラップそのものではありませんが…map
メソッドと??
演算子を組み合わせて、次のように同様の動作をするコードを書くこともできます。
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
print(maybeInt.map({ $0.description }) ?? "nilだよ。")
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
maybeInt.map({ print($0) }) ?? print("nilだよ。")
おわりに
Optional
のアンラップ方法はいくつもあることがお分かりいただけたでしょうか。二重三重のOptional
(たとえばInt??
とかString???
とか)を扱う場合はcase
を使うほうが便利な場合があります。
この記事を読んで読者の方々のSwiftコードの書き方の幅が広がれば幸いです。