目次

目次

【初心者向け】Swiftの”?”と”!”,はじめからていねいに (1/2)

アバター画像
河野穣
アバター画像
河野穣
最終更新日2019/12/09 投稿日2019/12/09

はじめに

初投稿になります.19新卒の河野です.配属後はiOSエンジニアとして,日々精進しています.今回の投稿では,Swiftの1大テーマである”?”と”!”について一度整理しようと思い,このテーマにしました.

既存の解説記事とはやや切り口が異なりますが,個人的にこのまとめ方で理解するのもわかりやすいのでは,と試行錯誤してまとめてみました.

型の後ろの”?”と”!”

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であるかそうでないかによって後続の処理を分岐させるためのより工夫された仕組みがあります.次回はそれらを中心についてまとめていこうと思います.

参考

アバター画像

河野穣

2019年新卒のエンジニアです.iOS修行中.

目次