Swift における「Optional 型」について…

朝の実(Asanomi)アイコン アプリ開発

Swift のコードでは String?Int? といった、 末尾に ? が付いた型によく出会います。

これらは Optional(オプショナル)型と呼ばれ、 Swift の安全性を支える重要な仕組みの一つです。

一方で、 「なぜ Optional 型が必要なのか」 「なぜそのまま使えないのか」 といった点で、最初は戸惑うことも多いと思います(私もその一人です)。

本記事では、Swift の公式ドキュメントをもとに、 Optional 型の役割と考え方を整理します。 あわせて、Optional を安全に扱うための構文である if letguard let についても、 それぞれの役割に焦点を当てて解説します。

オプショナル型とは?

Swift の公式ドキュメントでは、Optional 型について次のように説明されています。

You use optionals in situations where a value may be absent.

意訳すると、「Optional 型は値が存在しない可能性がある場面で使用する」となります。

Optional 型は、単に 「nil を代入できる型」 という意味ではありません。
Optional 型は、「値が存在するかどうか」という状態を含めて表現する型です。
そのため、StringString? は 見た目は似ていても、まったく別の型として扱われます。

なぜ Optional 型が必要なのか

Optional 型が必要とされる理由を理解するために、 まずは Java のコードを例に見てみます。

String name = null;
System.out.println(name.length()); // 実行時例外(NullPointerException)が発生する

このコードはコンパイル時には問題ありませんが、 実行時に NullPointerException が発生します。

Java では、 「この値が null かもしれない」という情報が 型として表現されていないため、 実行するまで問題に気づけません。

同じことを Swift で書こうとすると、次のようになります。

let name: String? = nil 
print(name.count) // コンパイルエラー

Swift では、Optional 型の値をそのまま使おうとするとコンパイルエラーになります。
そのため、コードを書いている段階で問題に気づくことができます。

Optional 型を扱うための構文

アンラップ(unwrap)

Optional 型の値は、そのままでは中身を使うことができません。
これは、値が存在しない可能性(nil)を無視したまま処理が進んでしまうことを、
Swift がコンパイル時に防ぐようになっているからです。

そのため Swift では、
Optional 型の値を実際に使う前に、
「中身が存在することを確認し、取り出す」必要があります。

この「Optional の中身を取り出す操作」のことを、
アンラップ(unwrap) と呼びます。

Swift では、このアンラップを安全に行うための構文として、
if letguard let が用意されています。

補足

公式ドキュメントでは、特に if let , guard let のアンラップを安全に行う構文を Optional Binding と呼んでいます。

if let

if let は、 条件分岐の中で Optional の中身を使いたい場合に使用します。

func printLength(text: String?) { 
    if let unwrappedText = text {
        // このブロック内では unwrappedText を String 型として使用可能
        print(unwrappedText.count) 
    } 
}

この例では、text が nil でない場合のみ、
if ブロックの中で unwrappedText を 通常の String として扱えます。

guard let

guard let は、 値が存在することを「処理を続けるための前提条件」として扱いたい場合に使用します。

func printLength(text: String?) {
    guard let unwrappedText = text else {
        return
    }
    // ここ以降は unwrappedText を String 型として使用可能

    print(unwrappedText.count)
}

この例では、text が nil の場合はその時点で処理を終了し、
nil でない場合のみ、以降の処理が続行されます。
そのため、guard の後では unwrappedText を 通常の String として
関数の残り全体で使用できます。

実際の使用例(朝の実)

私が現在開発中の朝専用SNSアプリ「朝の実」では、CloudKit の CKRecord をアプリ内モデルに変換する処理において guard let を使用しています。

CKRecord は、「キーに対応する値が存在しない」「想定していた型と異なる」といったケースが起こり得ます。そのため、モデル生成の前提として 必須フィールドがすべて正しく取得できること を保証する必要があります。

そこで、各フィールドの取得に as?(失敗すると nil を返すキャスト)を使い、その結果を guard let でまとめて検証・アンラップしています。
guard の条件部に複数の let … = … as? … を並べることで、どれか1つでも失敗した時点で処理を早期に終了できます。

一方、guard を抜けた後は、取得した値が すべて非Optionalとして揃っている状態になるため、以降の初期化処理を安全に記述できます。

import Foundation
import CloudKit

extension Post {
    init(record: CKRecord) throws {
        // as? は「型が合わない / 値がない」場合に nil を返す
        // guard let で必須項目をまとめてアンラップし、欠けていたら早期に失敗とする
        guard
            let id = record[PostRecordKey.id] as? String,
            let authorId = record[PostRecordKey.authorId] as? String,
            let text = record[PostRecordKey.text] as? String,
            let createdAt = record[PostRecordKey.createdAt] as? Date,
            let cheerCount = record[PostRecordKey.cheerCount] as? Int,
            let isDeletedInt = record[PostRecordKey.isDeleted] as? Int,
            let clientTimeZone = record[PostRecordKey.clientTimeZone] as? String
        else {
            throw CloudKitRepositoryError.decodingError
        }

        // guard let を抜けた後は、上の値はすべて「非Optional」として扱える
        self.id = id
        self.authorId = authorId
        self.text = text
        self.createdAt = createdAt
        self.cheerCount = cheerCount
        self.isDeleted = (isDeletedInt != 0)
        self.clientTimeZone = clientTimeZone
    }
}

まとめ

Optional 型は、 「値が存在しない可能性」を 型として明示するための仕組みです。

Swift では、 Optional 型をそのまま使うことはできず、 値が存在することを確認してから扱う必要があります。

if letguard let は、 Optional 型を安全に扱うために用意された構文であり、 用途に応じて使い分けることが重要です。

sho shimizu

SwiftUI を用いたiOS アプリの個人開発を行っています。
これまでに2本のアプリを App Store にリリースしました。

現在は、朝専用SNS「朝の実(Asanomi)」を開発中です。
CloudKitやSwift Concurrencyを用いた実装を進めながら、
設計や実装上で整理した内容を技術記事にまとめています。

sho shimizuをフォローする
アプリ開発技術解説朝の実
シェアする
sho shimizuをフォローする