📚 Swift入門シリーズ: #15 whileループ → #16 guard文
前回はguard文による早期リターンを学びました。今回は、Swiftの安全性を支える重要な機能であるオプショナル型について詳しく学びましょう。
来春からデータサイエンティストとして働く予定の技術オタク。
『知りたい』気持ちで質問を止められない、好奇心旺盛な学生。
前回はguard文で安全なコードの書き方を学んだね!
そうだね。今回は、Swiftの特徴的な機能の1つ、オプショナル型 について詳しく学んでいくよ。これは 第6章:nil安全 の始まりだね。
オプショナル?前にもちょっと出てきたけど、よく分からなかったんだよね...
大丈夫だよ。今回はオプショナル型の基本から、じっくり学んでいこう。これまでにも何度か ? や ! を見てきたと思うけど、今日でその意味がしっかり理解できるようになるよ。
nilとは何か
まず、nil について説明するね。nilは 「値がない」 ことを表す特別な値なんだ。
値がない?0とは違うの?
いい質問だね!0は「数値のゼロ」という 値 があるんだ。でもnilは 値そのものが存在しない という意味なんだよ。
let zero = 0 // 値は0(値がある)
let nothing: Int? = nil // 値がない
例えば、「年齢を入力してください」と聞いたとき、ユーザーが何も入力しなかった場合、0でもマイナスでもなく、「入力されていない」という状態を表すのがnilなんだ。
なるほど。「空っぽ」ってことか。
そうだね。でも、Swiftでは すべての型でnilを使えるわけではない んだ。
var name: String = "田中"
// name = nil // エラー!Stringはnilになれない
var optionalName: String? = "田中"
optionalName = nil // OK!String?はnilになれる
String? のように、型名の後に ? を付けた型を オプショナル型 って言って、これだけがnilを扱えるんだよ。
オプショナル型とは
オプショナル型 は、「値があるかもしれないし、ないかもしれない」という状態を表す型なんだ。
var age: Int? // Int型のオプショナル
age = 25 // 値がある状態
age = nil // 値がない状態
Int? は、「Intの値があるか、nilのどちらか」という意味なんだよ。
なんでそんな型が必要なの?普通の型じゃダメなの?
オプショナル型があることで、nilの可能性を型レベルで明示できる んだ。これによって、nilを扱うべき場所とそうでない場所が明確になって、バグを防げるんだよ。
func findUser(id: Int) -> String? {
// ユーザーが見つからない可能性がある
if id == 1 {
return "田中"
} else {
return nil // 見つからなかった
}
}
let user = findUser(id: 1) // String? 型
戻り値が String? だから、「nilが返ってくるかもしれない」ということが一目で分かるんだ。
オプショナル型の宣言
オプショナル型の宣言方法を見てみよう。
// オプショナル型の宣言
var name: String? // 初期値はnil
var age: Int? = 25 // 初期値を指定
var email: String? = nil // 明示的にnilを指定
print(name) // nil
print(age) // Optional(25)
print(email) // nil
オプショナル型を宣言して初期値を指定しないと、自動的にnilが入るんだ。
Optional(25) って何?25じゃないの?
いい質問だね!オプショナル型は、実際には 値をラップ(包んで) いるんだ。Optional(25) は「25という値が入っている」という意味なんだよ。
オプショナル型をそのまま使うことはできなくて、中の値を アンラップ(取り出す) 必要があるんだ。
let age: Int? = 25
// print(age + 5) // エラー!オプショナルは演算できない
オプショナル型は「箱」のようなもので、実際の値を使うには箱から取り出す必要があるんだよ。
アンラップの方法
オプショナルから値を取り出す方法はいくつかあるんだ。それぞれ見ていこう。
1. 強制アンラップ(!)
強制アンラップ は、! を使って値を強制的に取り出す方法なんだ。
let age: Int? = 25
let unwrappedAge = age!
print(unwrappedAge) // 25
print(unwrappedAge + 5) // 30
age! で値を取り出せるんだけど、これは 危険な方法 なんだ。
なんで危険なの?
nilの時に強制アンラップすると、クラッシュ するんだ。
let age: Int? = nil
// let unwrappedAge = age! // クラッシュ!
// Fatal error: Unexpectedly found nil while unwrapping an Optional value
nilが入っているオプショナルを ! でアンラップしようとすると、プログラムが強制終了してしまうんだよ。だから、強制アンラップは極力避けるべき なんだ。
怖い!じゃあどうすればいいの?
2. オプショナルバインディング(if let)
オプショナルバインディング が安全な方法なんだ。if let を使うんだよ。
let age: Int? = 25
if let unwrappedAge = age {
print("年齢: \(unwrappedAge)")
print("5年後: \(unwrappedAge + 5)")
} else {
print("年齢が設定されていません")
}
// 年齢: 25
// 5年後: 30
if let 変数 = オプショナル で、値がある場合だけifブロックが実行されるんだ。nilの場合はelseブロックが実行されるよ。
これなら安全だね!
そうなんだ。もう1つ例を見てみよう。
let name: String? = nil
if let unwrappedName = name {
print("名前: \(unwrappedName)")
} else {
print("名前が設定されていません")
}
// 名前が設定されていません
nilの場合は、ifブロックがスキップされてelseが実行されるから、クラッシュする心配がないんだよ。
3. オプショナルバインディング(guard let)
前回学んだguard文でもオプショナルバインディングができるんだ。
func greet(name: String?) {
guard let unwrappedName = name else {
print("名前がありません")
return
}
// ここでunwrappedNameが使える
print("こんにちは、\(unwrappedName)さん")
print("\(unwrappedName)さん、ようこそ!")
}
greet(name: "田中") // こんにちは、田中さん
// 田中さん、ようこそ!
greet(name: nil) // 名前がありません
guard letでアンラップした変数は、guard文の後でも使えるのが特徴なんだ。
4. 複数のオプショナルをまとめてアンラップ
複数のオプショナルを同時にアンラップすることもできるんだ。
let firstName: String? = "太郎"
let lastName: String? = "田中"
if let first = firstName, let last = lastName {
print("フルネーム: \(last) \(first)")
} else {
print("名前が不完全です")
}
// フルネーム: 田中 太郎
カンマで区切って、複数のオプショナルをアンラップできるんだよ。すべての値が存在する場合だけ、ifブロックが実行されるんだ。
これは便利!
guard文でも同じことができるよ。
func registerUser(firstName: String?, lastName: String?, age: Int?) {
guard let first = firstName,
let last = lastName,
let age = age else {
print("すべての情報を入力してください")
return
}
print("登録: \(last) \(first)さん(\(age)歳)")
}
registerUser(firstName: "太郎", lastName: "田中", age: 25)
// 登録: 田中 太郎さん(25歳)
registerUser(firstName: "太郎", lastName: nil, age: 25)
// すべての情報を入力してください
nil合体演算子(??)
nil合体演算子(??) を使うと、nilの場合のデフォルト値を指定できるんだ。
let name: String? = nil
let displayName = name ?? "ゲスト"
print("ようこそ、\(displayName)さん")
// ようこそ、ゲストさん
オプショナル ?? デフォルト値 という書き方で、オプショナルがnilの場合はデフォルト値が使われるんだよ。
これは便利!
もう1つ例を見てみよう。
let userAge: Int? = nil
let age = userAge ?? 0
print("年齢: \(age)") // 年齢: 0
let userName: String? = "田中"
let name = userName ?? "匿名"
print("名前: \(name)") // 名前: 田中
値がある場合はその値が、nilの場合はデフォルト値が使われるんだ。
nil合体演算子の連鎖
nil合体演算子は連鎖させることもできるんだ。
let firstName: String? = nil
let nickName: String? = nil
let defaultName = "ゲスト"
let displayName = firstName ?? nickName ?? defaultName
print("ようこそ、\(displayName)さん")
// ようこそ、ゲストさん
左から順番にチェックして、最初にnilでない値が使われるんだよ。
オプショナルチェイニング
オプショナルチェイニング を使うと、オプショナルのプロパティやメソッドに安全にアクセスできるんだ。
let name: String? = "田中"
// オプショナルチェイニング
let length = name?.count
print(length) // Optional(2)
name?.count は、nameがnilでない場合だけcountを取得するんだ。nameがnilの場合は、全体がnilになるんだよ。
?. って何?
?. は オプショナルチェイニング演算子 なんだ。オプショナルがnilでない場合だけ、その後のプロパティやメソッドにアクセスするんだよ。
let name: String? = nil
let length = name?.count
print(length) // nil
nameがnilの場合、lengthもnilになるんだ。クラッシュせずに安全にアクセスできるんだよ。
通常のアクセス(.)との違いを見てみよう。
let name: String? = "田中"
// 通常のアクセス
// let length = name.count // エラー!オプショナルは直接アクセスできない
// オプショナルチェイニング
let length = name?.count // OK!
オプショナルに対して直接 . でアクセスすることはできないから、?. を使う必要があるんだ。
メソッドのオプショナルチェイニング
メソッドにもオプショナルチェイニングが使えるんだ。
let text: String? = "Hello"
let uppercase = text?.uppercased()
print(uppercase) // Optional("HELLO")
let emptyText: String? = nil
let emptyUppercase = emptyText?.uppercased()
print(emptyUppercase) // nil
textがnilでない場合だけ、uppercased()が呼ばれるんだよ。
暗黙的アンラップオプショナル
最後に、暗黙的アンラップオプショナル について説明するね。これは ! を使って宣言するオプショナルなんだ。
var age: Int! // 暗黙的アンラップオプショナル
age = 25
print(age) // 25(自動的にアンラップされる)
print(age + 5) // 30
Int! は、オプショナルだけど、使うときに自動的にアンラップされるんだ。
これは便利そう!いつも使えばいいんじゃない?
いや、これも 危険 なんだ。nilの時にアクセスするとクラッシュするからね。
var age: Int! = nil
// print(age) // クラッシュ!
暗黙的アンラップオプショナルは、初期化時にはnilだけど、その後は必ず値がある ことが保証されている場合だけ使うべきなんだ。
初心者のうちは、普通のオプショナル(?)を使って、安全にアンラップする方法を選ぶことをおすすめするよ。
実践練習
じゃあ、練習問題をやってみよう。
問題1: オプショナルの配列 [1, nil, 3, nil, 5] から、nilでない値だけを取り出して合計を計算するコードを書いてみて。
やってみる!
let numbers: [Int?] = [1, nil, 3, nil, 5]
var sum = 0
for number in numbers {
if let unwrappedNumber = number {
sum += unwrappedNumber
}
}
print("合計: \(sum)")
// 合計: 9
できた!
完璧だね!オプショナルバインディングを正しく使えているよ。guard letを使った書き方もできるよ。
let numbers: [Int?] = [1, nil, 3, nil, 5]
var sum = 0
for number in numbers {
guard let unwrappedNumber = number else {
continue
}
sum += unwrappedNumber
}
print("合計: \(sum)")
あるいは、nil合体演算子を使う方法もあるね。
let numbers: [Int?] = [1, nil, 3, nil, 5]
var sum = 0
for number in numbers {
sum += number ?? 0
}
print("合計: \(sum)")
色々な方法があるんだね!
そうなんだ。状況に応じて適切な方法を選べるようになるといいね。
問題2: ユーザーの情報(名前、年齢、メールアドレス)をオプショナルで受け取って、すべて揃っていれば表示、どれか1つでもnilなら「情報が不完全です」と表示する関数を書いてみて。
func displayUserInfo(name: String?, age: Int?, email: String?) {
guard let name = name,
let age = age,
let email = email else {
print("情報が不完全です")
return
}
print("ユーザー情報")
print("名前: \(name)")
print("年齢: \(age)歳")
print("メール: \(email)")
}
displayUserInfo(name: "田中", age: 25, email: "tanaka@example.com")
// ユーザー情報
// 名前: 田中
// 年齢: 25歳
// メール: tanaka@example.com
displayUserInfo(name: "佐藤", age: nil, email: "sato@example.com")
// 情報が不完全です
こう?
素晴らしい!guard letを使って、複数のオプショナルをまとめてチェックできているね。オプショナル型の扱い方をしっかり理解できているよ。
まとめ
この記事では、オプショナル型について学びました。
オプショナル型の基本:
型?:値があるかnil- nilは「値がない」ことを表す
- 型レベルでnilの可能性を明示
アンラップの方法:
!:強制アンラップ(危険、避けるべき)if let:安全なアンラップguard let:早期リターンでアンラップ??:nil合体演算子(デフォルト値)?.:オプショナルチェイニング
重要なポイント:
- 強制アンラップ(
!)は極力避ける - オプショナルバインディングで安全にアンラップ
- nil合体演算子でデフォルト値を指定
- オプショナルチェイニングで安全にアクセス
暗黙的アンラップオプショナル:
型!で宣言- 自動的にアンラップされる
- 危険なので初心者は避けるべき
次回は「関数の基本」として、関数の定義と呼び出しについて学びます。コードを再利用可能にする重要な機能を身につけましょう!