Swiftのオプショナル型の使いこなし

2018年3月8日(木)
大津 真
連載の5回目となる今回は、値がないことを示すnilを扱えるオプショナル型変数について解説する。

はじめに

一般にプログラム言語では、何もない状態である「nil(もしくはnullなど)」をどのように処理するかが、堅牢なプログラム作成の重要な鍵となります。今回は、Objective-CにはないSwiftの機能として、nilを安全に処理する仕組みであるオプショナル型の取り扱いについて説明します。

オプショナル型の概要

Swiftの変数は、オプショナル型とそれ以外(非オプショナル型)に大別されます。Swiftにはnilという、ある型に値がない状態を表す特別な値がありますが、非オプショナル型の変数に格納できるのは通常の値のみで、nilを格納することはできません。それに対してオプショナル型の変数には、通常の値もしくはnilのどちらかを格納することができます。

オプショナル型はnilをとることができる

オプショナル型はnilをとることができる

アンラップして値を取り出す

オプショナル型の変数は、その中に格納されている値を直接使用することはできません。アンラップ(unwrap)という処理によって、値を取り出す必要があります。

オプショナル型の変数に格納されている値は「アンラップ」して取り出す

オプショナル型の変数に格納されている値は「アンラップ」して取り出す

アンラップの方法はいくつか用意されています。

アンラップする方法

アンラップする方法

オプショナル型の変数の用途

オプショナル型の変数は、文字通りオプション的なプロパティなどに多用されます。次の例は顧客情報を管理するCustomerクラスです。sex(性別)をオプショナル型としています。

リスト1:オプショナル型変数を含むクラスの例

class Customer{
    var name: String
    var sex: Int? // ← オプショナル型
    init(name: String) {
        self.name = name
    }
}

また、関数やメソッドの戻り値がnilかどうかで、正しく処理されたかどうかを判断するといった目的でも使用されます。

戻り値にnilを用いて、処理の成功/失敗を判別

戻り値にnilを用いて、処理の成功/失敗を判別

オプショナル型を使う

それでは実際に、簡単な例を通してオプショナル型の基本的な使用方法を見ていきましょう。

「型?」で宣言する

変数をオプショナル型として宣言するには、型アノテーションの型名の後ろに「?」を記述します。

リスト2:オプショナル型変数の宣言

var num1: Int? // 変数num1をオプショナル型として宣言(値はnil)

これは次の形式のシンタックスシュガーです。

リスト3:もう一つのオプショナル型変数の宣言

var num1:Optional<Int>

これで変数num1は、何もない状態(nil)、もしくは整数値を格納できるようになります。また、非オプショナル型の変数は初期化しないと使用できませんが、オプショナル型の変数は宣言した時点で「nil」となります。次にnum1に「10」を代入してprint文で表示する例を示します。

リスト4:オプショナル型変数は宣言した時点でnilとなる

var num1: Int? // 宣言した時点ではnil
num1 = 10
print(num1) // → "Optional(10)"

コードを入力すると、Xcodeの静的解析機能により「Expression implicitly coerced from 'Int?' to Any」(この式をオプショナル型のIntからAny型に強制的に変更します)という警告が表示され、実行するとコンソールに「Optional(10)」と表示されます。

オプショナル型の変数は直接使用できない

前述の通り、オプショナル型の変数はそのままでは使用できません。例えば、以下のような演算はエラーになります。

リスト5:オプショナル型変数を直接使用しようとするとエラーに

var num2 = num1 + 10 // ← エラー

「変数名!」で強制的にアンラップする

オプショナル型の変数を、強制的にアンラップして中身の値を取り出すには、変数名の後ろに「!」を記述します。

リスト6:変数名+「!」でアンラップ

var num1: Int? // 変数num1をオプショナル型として宣言
num1 = 10
var num2 = num1! + 10 // num1を強制的にアンラップ → num2は20

ただし、この方法では変数がnilの場合エラーとなります。上記の例の2行目をコメントにして試してみましょう。

リスト7:「!」でアンラップする際に値がnilだとエラーになる

var num1: Int? // 変数num1をオプショナル型として宣言
// num1 = 10  // コメントにする。num1はnil
var num2 = num1! + 10 // ← エラー

オプショナル型の値にメソッドを実行する場合も、同様にアンラップする必要があります。次の例を見てみましょう。

リスト8:メソッドを実行する際もアンラップする

var str1: String? = "ABCDEFG"  // (1)
str1!.removeLast() // (2)str1は"ABCDEF"

(1)でString型の変数str1をオプショナル型と宣言し、「ABCDEFG」を代入しています。(2)でstr1を強制的にアンラップし、removeLast()メソッドで最後の文字を取り除いています。この場合も、str1がnilの場合にはエラーになります。つまり、「変数名!」による強制アンラップでは、プログラムがクラッシュする危険があるということです。

「型!」で宣言することにより自動でアンラップする

オプショナル型の変数の宣言に「型名?」の代わりに「型名!」を指定すると、変数の使用時に「変数!」の「!」は不要で自動でアンラップしてくれます。これを暗黙的アンラップ・オプショナル型(Implicitly Unwrapped Optional)と言います。

リスト9:暗黙的アンラップ・オプショナル型

var num1: Int! // 「!」で変数num1を暗黙的アンラップ・オプショナル型として宣言
num1 = 10
var num2 = num1 + 10 // 自動でアンラップされる。 num2の値は「20」

この場合も、変数の値がnilの場合にはエラーとなります。

リスト10:暗黙的アンラップ・オプショナル型もnilの値を使用しようとするとエラーに

var num1: Int! // 「!」で変数num1を暗黙的アンラップ・オプショナル型として宣言
// num1 = 10  // コメントにする。num1はnil
var num2 = num1 + 10 // ← エラー

したがって、「型!」による暗黙的アンラップが許されるのは、使用時に変数の値が確実にnilでないとわかっている場合のみです。例えば、iOSアプリをStoryboardで作成する場合、GUI部品のアウトレットを設定すると、その変数が「型!」として暗黙的アンラップとして宣言されます。

リスト11:暗黙的アンラップを使用できる例

class ViewController: UIViewController {
    @IBOutlet weak var myLabel: UILabel! // ← アウトレットが暗黙的アンラップになる
    ~

上記の例はビューがロードされる時点で、アウトレットの変数には対応するGUI部品が接続されるため、通常は値がnilになることはないためです。

オプショナルバインディングで安全にアンラップ

「変数名!」のように強制的にアンラップしたり、「var 変数名: 型!」の形式で宣言して自動でアンラップしたりできるのは、確実に変数に値が入っている場合、つまりnilでない場合のみです。変数にnilが入っているとエラーとなり、プログラムが落ちます。通常は、以下のようにif文を使用して、変数がnilでないかどうかを判断して、処理を切り分けます。これをオプショナルバインディング(Optional Binding)と呼びます。

リスト12:オプショナルバインディング

if let 変数 = オプショナル型の変数 {
  //  nilでない場合の処理
} else {
  //  nilである場合の処理
}

オプショナル型の変数に値が入っている場合、その値がifの後ろの「let 変数」で指定した変数に代入されます(後から変数を変更する場合には、letをvarにします)。その場合、代入されるのはアンラップされた値です(「let 変数」の変数はオプショナル型にはならない点に注意してください)。

また、オプショナル型の変数がnilの場合には、ifの後ろの「let 変数」の変数に代入は行われず、変数は未定義値(unresolved identifier)となります。

次に、オプショナル型の変数str1がnilでない場合にはその中身を表示し、nilの場合には「str1はnil」と表示する例を示します。

リスト13:オプショナルバインディングの例

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文は、条件が成立しない場合にブロックで指定した処理を実行する構文です。

リスト14:guard文を用いたオプショナルバインディング

guard 条件 else {
  // 条件が偽であった場合の処理
}

guard文をオプショナルバインディングに使用するには、以下のようにします。

リスト15:guard文によるオプショナルバインディングの例

guard let 変数 = オプショナル型の変数  else {
  // 変数がnilであった場合の処理
}

以下の例のように関数の先頭にguard文を記述し、引数がnilの場合にreturn文で関数を抜けるというのがよくある使い方です。

リスト16:guard文によるオプショナルバインディングのよくある使い方

func myFunc(name: String?) {
    guard let unwrappedName = name else {return}

  // 引数がnilでない場合の処理
}

オプショナルチェイニングで安全にアンラップしてメソッドを実行

オプショナルバインディングと並んで、オプショナル型の変数を安全に扱う方法に、オプショナルチェイニング(Optional Chaining)があります。オプショナルチェイニングでは、オプショナル型の変数の後ろに「?」を記述してメソッドやプロパティにアクセスします。

オプショナル型の変数?.メソッド()
オプショナル型の変数?.プロパティ

こうすると、オプショナル型の変数に値が存在するかどうかを調べて、存在していればメソッドを実行したりプロパティを取得したりします。一方、値がnilの場合には、メソッドやプロパティは実行されずにnilが戻されます。サブスクリプト(配列のインデックスや辞書のキー)の場合にも、オプショナルチェイニングを指定できます。

オプショナル型の変数?[インデックスやキー]

「変数!」で強制的にアンラップする場合と、「変数?」でオプショナルチェイニングを使用する場合の相違を確かめてみましょう。次の例は、文字列「HEllO」が格納されたオプショナル型の変数str2を「!」で強制的にアンラップして、String型のlowercased()メソッドを実行して小文字に変換し、変数str2に代入しています。

リスト17:オプショナルチェイニングを使わない例

var str1: String? = "HEllO"
let str2 = str1!.lowercased() // ←(1)
print(str2) // → hello

結果として「hello」が表示されます。ただし、変数str1がnilの場合にはエラーとなり、プログラムが落ちます。

(1)の強制アンラップをオプショナルチェイニングに変更した例を示します。

リスト18:オプショナルチェニングを使う例

var str1: String? = "HEllO"
let str2 = str1?.lowercased() // オプショナルチェイニングに変更
print(str2) // → Optional("hello")

この場合、変数str2の値はオプショナル型になり、print()関数では「Optional("hello")」と表示されます。また、変数str1がnilの場合にも、エラーとならず、結果はnilとなります。オプショナルチェイニングの結果はオプショナル型となるため、しばしばオプショナルバインディングと組み合わせて使用されます。

リスト19:オプショナルチェイニングとオプショナルバインディングの組み合わせ

let str1: String? = "HEllO"
if let str2 = str1?.lowercased() {
    print(str2)
}

なお、オプショナルチェイニングでは、オプショナル型の変数がnilの場合に、その後ろのメソッドやプロパティは無視されます。これはメソッドやプロパティを複数接続した場合も同じです。次の例では、オプショナル型の変数str1に対して、小文字にするlowercased()メソッド、最初の数文字(以下の例では3)を取り除くdropFirst()メソッドを連続実行しています。

リスト20:

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にします。

リスト21:??演算子の例

var size: Int?
let defaultSize = 10
let yourSize = size ?? defaultSize // (1)sizeがnilの場合には10

なお、(1)の??演算子は次のような三項演算子と「!」による強制アンラップの組み合わせをシンプルにしたものと考えることができます。

リスト22:??演算子と同じ機能を三項演算子で実現

let yourSize = size != nil ? size! : defaultSize
Null Safetyについて

モダンなプログラミング言語ではNull Safety(Null安全や、Nullセーフなどと訳される)というのが重要なキーワードです。Null Safetyとは、null(Swiftの場合にはnil)に対して演算やメソッドなどを実行すると、実行時ではなく、コンパイル時(もしくは静的解析時)にエラーになる仕組みです。これにより、誤ってnullを操作して、実行時にアプリケーションがクラッシュするといった不具合を避けることができるわけです。例えば、Null SafetyではないJavaScriptの例を見てみましょう。

リスト23:Null SafetyでないJavaScriptの例

str1 = null;
requestAnimationFrame = str1.toUpperCase() ←(1)

上記のプログラムを実行すると、(1)でnullに対してtoUpperCase()メソッドが実行されアプリケーションが落ちてしまいます。

同じくObjective-CもNull Safetyではありませんが、仕様としてnilに対してメッセージを送ることが許されています。

リスト24:Null SafetyでないObjective-Cの例

NSString *str1 = nil;
NSString *str2 = [str1 uppercaseString]; // ← エラーにならずnilが戻る

上記の例ではコンパイルも通り実行時にもエラーになりませんが、逆に不具合を見つけにくくなってしまう可能性があります。

それに対して、Null SafetyなSwiftの場合には、オプショナル型が用意され、nilを取りうる値に対して直接処理を実行するコードを入力すると、解析時にエラーとなります。

リスト25:Null SafetyなSwiftの例

var str1: String? = nil
var str2 = str1.uppercased() // ← エラー

つまりオプショナルバインディングなどを使用してコードを修正しない限り、プログラムを実行できないわけです。

東京都生まれ。早稲田大学理工学部卒業後、外資系コンピューターメーカーにSEとして8年間勤務。現在はフリーランスのテクニカルライター、プログラマー。

<主な著書>
きちんとわかる! JavaScript とことん入門(技術評論社)
基礎Python(インプレス)
他多数

連載バックナンバー

開発言語技術解説
第9回

Swiftでの文字列操作の基礎(その1)

2019/9/27
連載9回目となる今回は、Swfitによる文字列操作の基本を学びます。あわせてSwift 5で導入された新機能にもついても紹介します。
開発言語
第8回

Swiftでクロージャを理解する

2018/12/21
連載8回目となる今回は、モダンな言語に欠かせないクロージャについて解説します。
開発言語技術解説
第7回

Swiftのユーザ定義関数の活用

2018/9/13
連載7回目となる今回は、Swiftのユーザ定義関数のさまざまな活用方法を紹介していく。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています