Swiftの「?」と「!」,はじめからていねいに

2020/12/05に公開
2

はじめに

Swiftでコードを書いていく際に?!のという記号が頻出します. XcodeでSwiftを用いた開発を行う際には補完機能で「Fix」ボタンをポチポチ押していけばそれとなくwarningも消えていきますが, これらがどういうものであるかを体系的に理解しておかないと, このようなポチポチフリースタイルも限界がきてしまいます.

そこでこの記事では, Swiftのコードの中で出てくる様々なケースの?!についてまとめてみました.

目次や「まとめ」に掲載した表を参考にしつつ逆引き的に参照すると便利かと思います.

型の後ろの"?"と"!"

Swiftでは変数を宣言する際に型の後ろに"?"・"!"をつけることがあります.これは変数をオプショナル型として宣言する場合につけるものです.Swiftでは変数にnilを代入することができません.nilの代入を許容するには,変数宣言するときにその変数をオプショナル型変数として宣言する必要があります.Swiftのオプショナル型変数にはオプショナル型(Optional Value)と暗黙的アンラップ型(Implicitly Unwrapped Optional)の2種類があります.

var hoge: Int //Int型(nilを許容しない)
var fuga: Int? //オプショナルInt型(nilを許容する)
var piyo: Int! //暗黙的アンラップInt型(nilを許容する)

オプショナル型変数と非オプショナル型変数は同じInt型であっても異なるデータ型という扱いになります.

var hoge: Int = 10 //Int型(nilを許容しない)
var fuga: Int? = 10 //オプショナルInt型(nilを許容する)
var piyo: Int! = 10 //暗黙的アンラップInt型(nilを許容する)

print(hoge) //10
print(fuga) //Optional(10)
print(piyo) //Optional(10)

したがって,これらを二項演算すると型が異なる変数同士を演算することになるためエラーになります.

var hoge: Int = 10//Int型(nilを許容しない)
var fuga: Int? = 10//オプショナルInt型(nilを許容する)

print(hoge + fuga)

//---- 以下出力されるエラー ----//
error: value of optional type 'Int?' must be unwrapped to a value of type 'Int'
print(hoge + fuga)

このとき,この演算を実行するためにはオプショナル型変数に対して,オプショナル変数に格納されている値を取り出すアンラップ (unwrap)という操作が必要になります.

オプショナル型変数のうち暗黙的アンラップ型は演算が実行されるタイミングで自動的に(暗黙的に)アンラップの操作を行います.

var hoge: Int = 10//Int型(nilを許容しない)
var piyo: Int! = 10//暗黙的アンラップInt型(nilを許容する)

print(piyo)//Optional(10)
print(hoge + piyo)//20
print(piyo == 10)//true

しかし,piyo = nilの場合にアンラップを行なった場合,nilと(nilを許容しない)非オプショナル型を二項演算することになるためエラーが発生します.したがって,nilの可能性があるからという理由で闇雲に変数を暗黙的オプショナル型として宣言することは危険です.

var hoge: Int = 10//Int型(nilを許容しない)
var piyo: Int! = nil//オプショナルInt型(nilを許容する)

print(piyo)//nil
print(hoge + piyo)//エラー
print(piyo == 10)//エラー

名前の後ろの"?"と"!"

Swiftのコードに出てくる"?"と"!"のうち,変数やメソッドなどの名前の後ろについてくる"?"と"!"は前述のアンラップに関わる操作を行うための記号です.名前の後ろに"?"や"!"をつける処理としてForced Unwrapping (強制的アンラップ)とOptional Chaining (オプショナルチェイニング)をここではまとめます.

<オプショナル型変数>! ← Forced Unwrapping (強制的アンラップ)
<クラス>.<オプショナル型のプロパティ>?.<プロパティ他>Optional Chaining (オプショナルチェイニング)

Forced Unwrapping (強制的アンラップ)

Forced Unwrapping (強制的アンラップ)とはオプショナル型変数の中にどんな値が入っていてもアンラップをするという方法です.以下の記法でオプショナル型変数を非オプショナル型変数に変換します.

<オプショナル型変数>!

var fuga: Int? = 10//オプショナルInt型(nilを許容する)

print(fuga)//Optional(10)
print(fuga!)//10

しかし,fugaにnilが入っている場合にもアンラップを強行しようとするため,その場合にはエラーになります.

var fuga: Int? = nil//オプショナルInt型(nilを許容する)

print(fuga!)//エラー

したがって,Forced Unwrappingによるアンラップを行う場合には,その変数に値が必ず格納されていることが保証されていることを確認して,行う必要があります.(公式ドキュメントにもこの"!"について以下のように記述がありました)

The exclamation mark effectively says, “I know that this optional definitely has a value; please use it.”

Optional Chaining (オプショナルチェイニング)

Optional Chaining (オプショナルチェイニング)とはクラスが持つオプショナル型のプロパティやメソッドを安全に呼び出す方法です.以下の例(公式ドキュメントを引用)をみてください.

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

このようにPersonクラスを定義した上でPersonクラスのインスタンスjohnのresidence.numberOfRoomsを取得することを考えます.以下の手順でnumberOfRoomsを取得しようとすると,エラーが発生します.

let john = Person()//この時点で特段イニシャライザの処理をしていないのでjohn.residenceはnil
let roomCount = john.residence!.numberOfRooms//john.residence!のアンラップ失敗でnumberOfRoomsが取得できずエラー
print(roomCount)//エラー

上のケースではオプショナル型のオブジェクトがnilを含む可能性のある状況下でForced Unwrappingをしたためにエラーが発生してしまいました.このような場面において,Optional Chainingを活用すれば,より安全にresidence.numberOfRoomsを取得することができます.

Optional Chainingでは以下の記法でプロパティやメソッドを参照します.

<クラス>.<オプショナル型のプロパティ/メソッド>?.<プロパティ/メソッド>...

Optional Chainingは,Forced Unwrappingとは異なり,この<オプショナル型のプロパティ/メソッド>がnilだった時点で,それ以降のプロパティやメソッドを参照せずにnilを返します.

let john = Person()

if john.residence?.numberOfRooms != nil {
    let roomCount = john.residence?.numberOfRooms ?? 0
    print(roomCount)
} else {
    print("住所なし")//こちらが出力される
}

したがって,強制的なアンラップを行わずに安全にオプショナル型のオブジェクトを扱うことができます.上の例でも存在しないかもしれないjohn.residence.numberOfRoomsを直でアクセスせずに,residenceがnilかそうでないかによって後続の処理を変えることができます.

さらに,前述したようなオプショナル型の変数がnilであるかそうでないかによって後続の処理を分岐させるためのより工夫された仕組みがあります.

Optional Binding (オプショナルバインディング)

前述のようにオプショナル型の変数がnilであるかそうでないかによって後続の処理を分岐させるための仕組みとして,Optional Bindingという仕組みがあります。Optional Bindingには以下の2つの構文があります.

  • if let
  • guard let

if let

if let 文は以下のような構文で分岐させる処理を記述します.

if let <変数> = <オプショナル型変数> { 
    // <オプショナル型変数>にnilが含まれていない場合の処理
} else {
    // <オプショナル型変数>にnilが含まれている場合の処理
}

if let 文では条件式右辺の<オプショナル型変数>にnilが含まれていない場合,アンラップした値をローカル変数である左辺の<変数>として,ブロック内で使用することができます.

var fuga : Int? = 10//オプショナルInt型(nilを許容する)

if let _fuga = fuga { 
    print(_fuga)
} else {
    print("にる")
}

//----出力----
//10

したがって,冒頭で再掲した前回のPerson型の例は以下のように記述することができます.

let john = Person()
john.residence = Residence()

if let _numberOfRooms = john.residence?.numberOfRooms {
    print(_numberOfRooms)
} else {
    print("住所なし")//こちらが出力される
}
let john = Person()
john.residence = Residence()//ここを追加

if let _numberOfRooms = john.residence?.numberOfRooms {
    print(_numberOfRooms)//こちらが出力される(1)
} else {
    print("住所なし")
}

guard let

guard let 文を用いることで,アンラップ時にオプショナル型変数にnilが含まれている場合,その時点で処理を中断させることができます.guard let 文は関数の中でのみ使うことができます.(この記事で使う例を検証してる時にめっちゃハマった)

guard let <変数> = <オプショナル型変数> else { return }
//後続処理

func printFuga(_ fuga: Int?) {
  guard let _fuga = fuga else { return }
    print(_fuga)
}

var fuga : Int? = 10//オプショナルInt型(nilを許容する)
printFuga(fuga)

//----出力----
//10

guard let 文では,if let 文と異なり,アンラップした値(_fuga)をguard let 文以降でも用いることができます.

式の中にある"?"と"??"

変数の型や変数名の後ろ以外にも,演算子として"?"が登場する場合があります."?"・"??"でそれぞれ意味が異なります.

三項演算子 "?"

"?"が1文字の場合は三項演算子の意味を持ちます.構文は以下の通りです.

変数 = 条件式 ? (真の場合の値) : (偽の場合の値)

let a = 10
let b = 20

let result = a == b ? "EQUAL" : "NOT EQUAL"
print(result)//NOT EQUAL

三項演算子自体はオプショナル型と直接関係はありません.

Nil coalescing operator "??"

"??"が2文字の場合はNil coalescing operatorと呼ばれる演算子の意味を持ちます.

変数 = オプショナル型変数 ?? (nilの場合の値)

三項演算子"?"が条件式の真偽によって評価の値を分岐させるのに対して,Nil coalescing operator "??" はオプショナル型変数がnilか否かで評価の値が分岐します.上の例ではオプショナル型変数の値がnilでなければその値が,nilの場合は??の右側にある(nilの場合の値)が変数に代入されます.

オプショナル型変数の値がnilだった場合に代入するデフォルト値などを扱うのに便利です.

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

let _numberOfRooms = john.residence?.numberOfRooms ?? 0
print(_numberOfRooms.description)//0

asの後ろの"?"と"!"

最後にまとめる"?"・"!"はas演算子の後ろにつくものです.as?・as!演算子は型キャストのうちダウンキャストを行う際に用いるものです.

as?

ダウンキャストが失敗する可能性があり実際に失敗した場合,nilを返します.また,成功時にはオプショナル型を返します.

let any: Any = "abc"
let str = any as? String   //Optional("abc")
let num = any as? Int      //nil

as!

ダウンキャスト時に強制的に型キャストを行います.失敗した場合,その時点でエラーを出します.

let any: Any = "abc"
let str = any as! String   //String型"abc"
let num = any as! Int      //Int型にキャスト失敗(実行時エラー)

まとめ

今回の記事で扱った"?"・"!"のまとめです.

参考

おまけ

こちらの内容は私が執筆した勤務先のブログにも掲載されています。こちらもよろしくお願いします。

Discussion

SGThr7SGThr7

Nil coalescing operatorの説明コードが間違っていませんか?
それとも、3項を取る??演算子があるのでしょうか

Yutaka KonoYutaka Kono

コメントありがとうございます!
ご指摘の通り、記事に誤りがありましたので修正しました。