Swiftのオプショナル型の使いこなし
はじめに
一般にプログラム言語では、何もない状態である「nil(もしくはnullなど)」をどのように処理するかが、堅牢なプログラム作成の重要な鍵となります。今回は、Objective-CにはないSwiftの機能として、nilを安全に処理する仕組みであるオプショナル型の取り扱いについて説明します。
オプショナル型の概要
Swiftの変数は、オプショナル型とそれ以外(非オプショナル型)に大別されます。Swiftにはnilという、ある型に値がない状態を表す特別な値がありますが、非オプショナル型の変数に格納できるのは通常の値のみで、nilを格納することはできません。それに対してオプショナル型の変数には、通常の値もしくはnilのどちらかを格納することができます。
アンラップして値を取り出す
オプショナル型の変数は、その中に格納されている値を直接使用することはできません。アンラップ(unwrap)という処理によって、値を取り出す必要があります。
アンラップの方法はいくつか用意されています。
オプショナル型の変数の用途
オプショナル型の変数は、文字通りオプション的なプロパティなどに多用されます。次の例は顧客情報を管理するCustomerクラスです。sex(性別)をオプショナル型としています。
class Customer{ var name: String var sex: Int? // ← オプショナル型 init(name: String) { self.name = name } }
また、関数やメソッドの戻り値がnilかどうかで、正しく処理されたかどうかを判断するといった目的でも使用されます。
オプショナル型を使う
それでは実際に、簡単な例を通してオプショナル型の基本的な使用方法を見ていきましょう。
「型?」で宣言する
変数をオプショナル型として宣言するには、型アノテーションの型名の後ろに「?」を記述します。
var num1: Int? // 変数num1をオプショナル型として宣言(値はnil)
これは次の形式のシンタックスシュガーです。
var num1:Optional<Int>
これで変数num1は、何もない状態(nil)、もしくは整数値を格納できるようになります。また、非オプショナル型の変数は初期化しないと使用できませんが、オプショナル型の変数は宣言した時点で「nil」となります。次にnum1に「10」を代入してprint文で表示する例を示します。
var num1: Int? // 宣言した時点ではnil num1 = 10 print(num1) // → "Optional(10)"
コードを入力すると、Xcodeの静的解析機能により「Expression implicitly coerced from 'Int?' to Any」(この式をオプショナル型のIntからAny型に強制的に変更します)という警告が表示され、実行するとコンソールに「Optional(10)」と表示されます。
オプショナル型の変数は直接使用できない
前述の通り、オプショナル型の変数はそのままでは使用できません。例えば、以下のような演算はエラーになります。
var num2 = num1 + 10 // ← エラー
「変数名!」で強制的にアンラップする
オプショナル型の変数を、強制的にアンラップして中身の値を取り出すには、変数名の後ろに「!」を記述します。
var num1: Int? // 変数num1をオプショナル型として宣言 num1 = 10 var num2 = num1! + 10 // num1を強制的にアンラップ → num2は20
ただし、この方法では変数がnilの場合エラーとなります。上記の例の2行目をコメントにして試してみましょう。
var num1: Int? // 変数num1をオプショナル型として宣言 // num1 = 10 // コメントにする。num1はnil var num2 = num1! + 10 // ← エラー
オプショナル型の値にメソッドを実行する場合も、同様にアンラップする必要があります。次の例を見てみましょう。
var str1: String? = "ABCDEFG" // (1) str1!.removeLast() // (2)str1は"ABCDEF"
(1)でString型の変数str1をオプショナル型と宣言し、「ABCDEFG」を代入しています。(2)でstr1を強制的にアンラップし、removeLast()メソッドで最後の文字を取り除いています。この場合も、str1がnilの場合にはエラーになります。つまり、「変数名!」による強制アンラップでは、プログラムがクラッシュする危険があるということです。
「型!」で宣言することにより自動でアンラップする
オプショナル型の変数の宣言に「型名?」の代わりに「型名!」を指定すると、変数の使用時に「変数!」の「!」は不要で自動でアンラップしてくれます。これを暗黙的アンラップ・オプショナル型(Implicitly Unwrapped Optional)と言います。
var num1: Int! // 「!」で変数num1を暗黙的アンラップ・オプショナル型として宣言 num1 = 10 var num2 = num1 + 10 // 自動でアンラップされる。 num2の値は「20」
この場合も、変数の値がnilの場合にはエラーとなります。
var num1: Int! // 「!」で変数num1を暗黙的アンラップ・オプショナル型として宣言 // num1 = 10 // コメントにする。num1はnil var num2 = num1 + 10 // ← エラー
したがって、「型!」による暗黙的アンラップが許されるのは、使用時に変数の値が確実にnilでないとわかっている場合のみです。例えば、iOSアプリをStoryboardで作成する場合、GUI部品のアウトレットを設定すると、その変数が「型!」として暗黙的アンラップとして宣言されます。
class ViewController: UIViewController { @IBOutlet weak var myLabel: UILabel! // ← アウトレットが暗黙的アンラップになる ~
上記の例はビューがロードされる時点で、アウトレットの変数には対応するGUI部品が接続されるため、通常は値がnilになることはないためです。
オプショナルバインディングで安全にアンラップ
「変数名!」のように強制的にアンラップしたり、「var 変数名: 型!」の形式で宣言して自動でアンラップしたりできるのは、確実に変数に値が入っている場合、つまりnilでない場合のみです。変数にnilが入っているとエラーとなり、プログラムが落ちます。通常は、以下のようにif文を使用して、変数がnilでないかどうかを判断して、処理を切り分けます。これをオプショナルバインディング(Optional Binding)と呼びます。
if let 変数 = オプショナル型の変数 { // nilでない場合の処理 } else { // nilである場合の処理 }
オプショナル型の変数に値が入っている場合、その値がifの後ろの「let 変数」で指定した変数に代入されます(後から変数を変更する場合には、letをvarにします)。その場合、代入されるのはアンラップされた値です(「let 変数」の変数はオプショナル型にはならない点に注意してください)。
また、オプショナル型の変数がnilの場合には、ifの後ろの「let 変数」の変数に代入は行われず、変数は未定義値(unresolved identifier)となります。
次に、オプショナル型の変数str1がnilでない場合にはその中身を表示し、nilの場合には「str1はnil」と表示する例を示します。
var str1: String? = "Hello" if let s = str1 { print(s) ←(1) } else { print("strはnil") }
上記の例の場合、オプショナル型の変数str1に「Hello」が格納されているため、変数sはアンラップされた値が代入され(1)のprint()関数で「Hello」と表示されます(アンラップされているため、Optional("Hello")ではありません)。
guard文でオプショナルバインディング
なお、Swift 2以降で搭載されたguard文を使用しても、オプショナルバインディングが行えます。guard文は、条件が成立しない場合にブロックで指定した処理を実行する構文です。
guard 条件 else { // 条件が偽であった場合の処理 }
guard文をオプショナルバインディングに使用するには、以下のようにします。
guard let 変数 = オプショナル型の変数 else { // 変数がnilであった場合の処理 }
以下の例のように関数の先頭にguard文を記述し、引数がnilの場合にreturn文で関数を抜けるというのがよくある使い方です。
func myFunc(name: String?) { guard let unwrappedName = name else {return} // 引数がnilでない場合の処理 }
オプショナルチェイニングで安全にアンラップしてメソッドを実行
オプショナルバインディングと並んで、オプショナル型の変数を安全に扱う方法に、オプショナルチェイニング(Optional Chaining)があります。オプショナルチェイニングでは、オプショナル型の変数の後ろに「?」を記述してメソッドやプロパティにアクセスします。
オプショナル型の変数?.メソッド()
オプショナル型の変数?.プロパティ
こうすると、オプショナル型の変数に値が存在するかどうかを調べて、存在していればメソッドを実行したりプロパティを取得したりします。一方、値がnilの場合には、メソッドやプロパティは実行されずにnilが戻されます。サブスクリプト(配列のインデックスや辞書のキー)の場合にも、オプショナルチェイニングを指定できます。
オプショナル型の変数?[インデックスやキー]
「変数!」で強制的にアンラップする場合と、「変数?」でオプショナルチェイニングを使用する場合の相違を確かめてみましょう。次の例は、文字列「HEllO」が格納されたオプショナル型の変数str2を「!」で強制的にアンラップして、String型のlowercased()メソッドを実行して小文字に変換し、変数str2に代入しています。
var str1: String? = "HEllO" let str2 = str1!.lowercased() // ←(1) print(str2) // → hello
結果として「hello」が表示されます。ただし、変数str1がnilの場合にはエラーとなり、プログラムが落ちます。
(1)の強制アンラップをオプショナルチェイニングに変更した例を示します。
var str1: String? = "HEllO" let str2 = str1?.lowercased() // オプショナルチェイニングに変更 print(str2) // → Optional("hello")
この場合、変数str2の値はオプショナル型になり、print()関数では「Optional("hello")」と表示されます。また、変数str1がnilの場合にも、エラーとならず、結果はnilとなります。オプショナルチェイニングの結果はオプショナル型となるため、しばしばオプショナルバインディングと組み合わせて使用されます。
let str1: String? = "HEllO" if let str2 = str1?.lowercased() { print(str2) }
なお、オプショナルチェイニングでは、オプショナル型の変数がnilの場合に、その後ろのメソッドやプロパティは無視されます。これはメソッドやプロパティを複数接続した場合も同じです。次の例では、オプショナル型の変数str1に対して、小文字にするlowercased()メソッド、最初の数文字(以下の例では3)を取り除くdropFirst()メソッドを連続実行しています。
var str1: String? = "ABCDEFG" if let str2 = str1?.lowercased().dropFirst(3) { // (1) print(str2) // (2) → "defg" } else { print("str1はnil") // (3) }
上記のように変数str1が「ABCDEFG」の場合には、(1)でlowercase()メソッドとdropFirst()メソッドが実行され、(2)で「defg」が表示されます。一方、変数str1がnilの場合には、(1)の2つのメソッドは無視され、(3)のprint()関数で「str1はnil」と表示されます。
??演算子でnilであった場合のデフォルト値を設定する
??演算子(Nil-Coalescing Operator)を使用すると、オプショナル型の変数をより便利に扱えます。
オプショナル型の変数 ?? デフォルト値
??演算子は、左辺のオプショナル型の変数がnilでない場合にはその値を戻し、nilの場合には右辺のデフォルト値を戻します。以下の例では、オプショナル型の変数sizeがnilの場合、変数yourSizeの値を10にします。
var size: Int? let defaultSize = 10 let yourSize = size ?? defaultSize // (1)sizeがnilの場合には10
なお、(1)の??演算子は次のような三項演算子と「!」による強制アンラップの組み合わせをシンプルにしたものと考えることができます。
let yourSize = size != nil ? size! : defaultSize
モダンなプログラミング言語ではNull Safety(Null安全や、Nullセーフなどと訳される)というのが重要なキーワードです。Null Safetyとは、null(Swiftの場合にはnil)に対して演算やメソッドなどを実行すると、実行時ではなく、コンパイル時(もしくは静的解析時)にエラーになる仕組みです。これにより、誤ってnullを操作して、実行時にアプリケーションがクラッシュするといった不具合を避けることができるわけです。例えば、Null SafetyではないJavaScriptの例を見てみましょう。
str1 = null; requestAnimationFrame = str1.toUpperCase() ←(1)
上記のプログラムを実行すると、(1)でnullに対してtoUpperCase()メソッドが実行されアプリケーションが落ちてしまいます。
同じくObjective-CもNull Safetyではありませんが、仕様としてnilに対してメッセージを送ることが許されています。
NSString *str1 = nil; NSString *str2 = [str1 uppercaseString]; // ← エラーにならずnilが戻る
上記の例ではコンパイルも通り実行時にもエラーになりませんが、逆に不具合を見つけにくくなってしまう可能性があります。
それに対して、Null SafetyなSwiftの場合には、オプショナル型が用意され、nilを取りうる値に対して直接処理を実行するコードを入力すると、解析時にエラーとなります。
var str1: String? = nil var str2 = str1.uppercased() // ← エラー
つまりオプショナルバインディングなどを使用してコードを修正しない限り、プログラムを実行できないわけです。