【プログラミング初心者】Swift基礎~オプショナル型~

はじめに

今回はオプショナル型について説明します。

オプショナル型とは

以前説明しましたが、Swiftの変数は基本的にメモリ上のどこかのアドレスを参照しています。
ですが、どこも参照していないということもあります。
このどこも参照していないという状態をnilといいます。(他言語ではnullと呼びます。)
nilは空っぽというような意味で何の値も持ちません。値というよりは何もないという「状態」と言った方が適切です。

Swiftでは変数は通常nilを許容しません。
ですが値を定義できないことも多くあり、nilを使いたいときがあります。
そんな場合に変数をオプショナル型で宣言してあげます。
宣言は以下のようになります。

var value: String?

このように型の後ろに?を付けることでオプショナル型として宣言できます。
普段変数を宣言する際は初期値として何かしらの値を入れていました。
ですがオプショナル型の場合、初期化しなければ暗黙的にnilとして宣言されます。

オプショナル型以外の通常の型にnilを入れるとどうなるでしょうか?

var value = "non-optional"
value = nil

'nil' cannot be assigned to type 'String'と怒られます。
このようにnilが入る可能性があるものはオプショナル型として宣言しなければなりません。

nilを使う例

実際のコードを見ないとイメージがつかないかもしれません。
nilを使うパターンは多くありますが一例を紹介します。

例えば人間構造体とペット構造体があり、人間はペットをメンバに持つとして以下のように定義されていたとします。

struct Human {
    var name: String
    var pet: Pet
}

struct Pet {
    var name: String
}

この場合、オブジェクトの生成は以下のようにできます。

let pet = Pet(name: "ポチ")
let human = Human(name: "山田太郎", pet: pet)

ではペットを飼っていない人はどのように定義するのでしょう。
上記の例ではpetは初期化の際必ず設定しなければなりません。

その場合ペットをオプショナル型として宣言し、飼っていない状態はnilとして扱います。

struct Human {
    var name: String
    var pet: Pet?
}

struct Pet {
    var name: String
}

let human = Human(name: "山田太郎", pet: nil)

あくまで一例ですが、このように定義できないときにnilを使用します。

オプショナル型のメリット

nilを扱える型としてオプショナル型について説明しました。
ではなぜオプショナル型というものがあるのでしょうか。
全ての型がnilを許容できればそんな面倒なことをしなくてもいいのでは、と思うかもしれません。
実際オプショナル型がなくnilを許容している言語が大多数で、もう一つのiOS開発言語であるObjective-Cでもオプショナル型はありません。

オプショナル型がないと仮定して以下のように実装します。

func addition(value1: Int, value2: Int) -> Int {
    return value1 + value2
}

let result = addition(value1: 10, value2: nil)

この場合resultはどんな値になるでしょうか?
答えは「計算できずアプリがクラッシュする」です。

このようにnilを参照してしまうとプログラムは動作できなくなりクラッシュします。
このとき発生するエラーは「null pointer エラー」などと呼ばれ、略してヌルポなどと呼ばれることもあります。

上記の例でクラッシュしないようにするためには値がnilかどうか判定し、nilの場合はどうするか処理を書いてあげる必要があります。

func addition(value1: Int, value2: Int) -> Int {
    if value1 == nil || value2 == nil {
        return 0
    } 
    return value1 + value2
}

let result = addition(value1: 10, value2: nil)

このように値がnilかどうか判定することをnullチェックと言います。
nullチェックは必要かどうかを実装者が判断して適宜行う必要があります。

さてオプショナル型のメリットですが、オプショナル型があることでnullとなり得る変数に対してnullチェックを行っていないとSwiftからエラーを出してくれるようになります。
(後述しますがこのnullチェックをアンラップと呼びます)

Swiftがエラーを出すというのは一見面倒なように聞こえるかもしれませんが、アプリとして想定外の処理が発生しないようにプラグラム教えてくれるというのはアプリのクラッシュを減らすことに繋がります。
iOSでもObjective-CからSwiftへ移行したことでアプリが落ちるというのは格段に減りました。

このようにオプショナル型のおかげで安全なコードを書くことができます。

アンラップ

オプショナル型を扱っていくにはアンラップする必要があります。
アンラップとはオプショナル型の変数を非オプショナル型である通常の変数に変換する手法です。
オプショナル型と非オプショナル型はアンラップしなければ同列で扱えません。

let value1: Int = 10
let value2: Int? = 20

let result = value1 + value2 // エラー

理由は先述した通りオプショナル型はnilの可能性があるためです。

それではアンラップの方法を紹介していきます。
アンラップの方法は何通りかあります。

if-let

使い方は以下となります。

let value = 10
let optionalValue: Int? = 20

if let unwrappedValue = optionalValue {
    let result = value + unwrappedValue 
    print(result) // 30
} else {
    print("optionalValue is nil")
}

if letで新しくオプショナル型ではない型としてunwrappedValueを宣言しています。
optionalValueがnilでなかった場合のみifの処理ブロックに入ります。
処理ブロックの中ではunwrappedValueは非オプショナル型として扱えます。

optionalValueがnilの場合はelseの処理ブロックに入ります。
elseのブロックは省略可能です。
if-elseのnullチェック版と捉えてもらえればいいです。

複数の値を同時にアンラップすることも可能です。

let optionalValue1: Int? = 10
let optionalValue2: Int? = 20

if let unwrappedValue1 = optionalValue1, let unwrappedValue2 = optionalValue2 {
    let result = unwrappedValue1 + unwrappedValue2
    print(result) // 30
} else {
    print("unwrappedValue1 or unwrappedValue2 is nil")
}

この場合はoptionalValue1optionalValue2が両方ともnilでない場合ifの処理ブロックに入ります。
どちらか一方でもnilだった場合はelseのブロックに入ります。

guard-let-else

こちらが最もよく使う構文になるかと思います。
使い方は以下となります。

let value = 10
let optionalValue: Int? = 20

guard let unwrappedValue = optionalValue else {
    print("optionalValue is nil")
    return
}
let result = value + unwrappedValue
print(result) // 30

optionalValueがnilでなかった場合、guard-let文以降unwrappedValueを非オプショナル型として扱えます。
optionalValueがnilの場合elseのブロックに入ります。
elseのブロックは省略不可です。
注意が必要なのはelseのブロックの最後は必ずreturnしてあげる必要があります。
return以降は当然後続の処理は実行されません。
optionalValueがnilだったら後続処理を実行できない場合などに使用します。

if-let同様に複数同時に宣言することも可能です。

let optionalValue1: Int? = 10
let optionalValue2: Int? = 20

guard let unwrappedValue1 = optionalValue1, let unwrappedValue2 = optionalValue2 else {
    print("unwrappedValue1 or unwrappedValue2 is nil")
    return
}

let result = unwrappedValue1 + unwrappedValue2
print(result) // 30

また戻り値がある場合、returnで値を何か返す必要があります。

func addition(optionalValue1: Int?, optionalValue2: Int?) -> Int {
    guard let unwrappedValue1 = optionalValue1, let unwrappedValue2 = optionalValue2 else {
        print("unwrappedValue1 or unwrappedValue2 is nil")
        return 0
    }
    return unwrappedValue1 + optionalValue2
}

print(addition(value1: 10, value2: 20))   // 30
print(addition(value1: 10, value2: nil))  // 0

Nil結合演算子(Nil-Coalescing Operator)

Nil結合演算子と呼ばれる??という演算子を使った方法があります。
オプショナル型の値がnilの場合、デフォルト値のようなものを与えることができます。

// nilでない場合
let value: Int = 10
let optionalValue: Int? = 20

let result = value + optionalValue ?? 0
print(result)  // 30
----------
// nilの場合
let value: Int = 10
let optionalValue: Int? = nil

let result = value + optionalValue ?? 0
print(result)  // 10

optionalValueがnilでない場合、optionalValue ?? 0の結果はoptionalValueをアンラップした値になります。
その結果「10 + 20」がresultに格納されました。

optionalValueがnilの場合、optionalValue ?? 0の結果はデフォルト値の「0」となります。
その結果「10 + 0」がresultに格納されたというわけです。

この方法は簡単なのでデフォルト値を決められるときはよく使います。

強制アンラップ(Forced Unwrapping)

あまりおすすめしない方法です。
nilかどうかに関わらずアンラップします。
変数がnilだった場合はアプリがクラッシュします。

let optionalValue1: Int? = 10
let optionalValue2: Int? = 20

let result = optionalValue1! + optionalValue2!
print(result) // 30
----------------------------
// クラッシュする例
let optionalValue1: Int? = 10
let optionalValue2: Int? = nil

let result = optionalValue1! + optionalValue2! // ここでnilをアンラップしてクラッシュする
print(result)

オプショナル型のメリットがなくなるのでサンプルコード以外での使用は控えたいところです。

メソッドの戻り値

メソッドの戻り値をオプショナル型とすることも可能です。
その場合、返ってきた値がオプショナル型となるので使用するときは別途アンラップをする必要があります。

func addition(optionalValue1: Int?, optionalValue2: Int?) -> Int? {
    guard let unwrappedValue1 = optionalValue1, let unwrappedValue2 = optionalValue2 else {
        return nil
    }
    return unwrappedValue1 + optionalValue2
}

print(addition(value1: 10, value2: 20))   // Optional(30)
print(addition(value1: 10, value2: nil))  // nil

オプショナルチェーン(Optional Chaining)

アンラップとは少し違いますがオプショナルチェーンという方法でオブジェクトのメソッドにアクセスすることができます。

class Human {
    var name: String
    init(name: String) {
        self.name = name
    }

    func speak() {
        print("私は" + self.name + "です")
    }
}

let human: Human? = Human(name: "山田太郎")
// オプショナルチェーン
human?.speak()  // "私は山田太郎です"

このようにオブジェクトの後ろに?を付けるとアンラップせずにメソッドを呼び出すことができます。
humanがnilの場合はメソッドは実行されずに無視されます。

let human: Human? = nil
human?.speak() // 実行されず無視される

このように?を付けておくと暗黙的にnullチェックされ、nilの場合は処理自体が無視されるので安全に呼び出すことができます。

またChainingというようにオプショナル型のオブジェクトを繋げていくことができます。

class Human {
    var name: String
    var friend: Human?

    init(name: String, friend: Human?) {
        self.name = name
        self.friend = friend
    }

    func speak() {
        print("私は" + self.name + "です")
    }
}

let friend: Human = Human(name: "山田次郎", friend: nil)
let human: Human? = Human(name: "山田太郎", friend: friend)
// オプショナルチェーン
human?.friend?.speak()  // "私は山田次郎です"
friend?.friend?.speak() // 実行されない

また戻り値があるメソッドの場合、メソッドの戻り値の定義が非オプショナル型であってもオプショナル型に変換されます。
オブジェクトがnilの場合は戻り値もnilとなります。

class Human {
    var name: String
    init(name: String) {
        self.name = name
    }

    func getName() -> String? {
        return self.name
    }
}

// オブジェクトがnilでない場合
let human: Human? = Human(name: "山田太郎")
let name = human?.getName()
print(name) // Optional("山田太郎")
-----------------
// オブジェクトがnilの場合
let human: Human? = nil
let name = human?.getName()
print(name) // nil

最後に

今回はオプショナル型、そしてその扱い方について紹介しました。
Javaなど他の言語にはない独特な書き方ですが慣れてしまえばかなり便利なものになります。
書いていればいずれ慣れるので最初のうちは調べながら徐々に慣れていきましょう。

今回の内容は以上です。

コメント

タイトルとURLをコピーしました