【Swift】辞書型

辞書型とはkeyと値を紐付けたデータを複数代入できる型のことで、アクセスするときは、keyを指定してそのkeyに紐付く値にアクセスします。
辞書型という名前の通り、辞書を引くような使い方をしたい時などに使います。
配列とは違い、インデックス番号は使えません。

前回は while文 について書きました。

今回は 辞書型 について書いていこうと思います。

辞書型とは

辞書型とは key を紐付けたデータを複数代入できる型のことで、アクセスするときは key を指定して、その key に紐付く にアクセスします。
辞書型という名前の通り、辞書を引くような使い方をしたい時などに使います。
配列とは違い、インデックス番号は使えません。

配列型 についてはこちらの記事で紹介しています。

辞書型の書式

//書式1
[keyの型名:値の型名]

//書式2
Dictionary<keyの型名,値の型名>

書式 1
[ ] の中に key の型名を書いて : で区切って値の型名を書きます。
型推論を使って定義することも可能です。

書式 2
Dictionary キーワードを書いて < > の中に key の型名を書いて , で区切って値の型名を書きます。

辞書型を使った例

//例1(書式1)
var geregere:[String:Int]
geregere = ["HP": 200, "MP": 70, "ATTACK": 120]

print(type(of: geregere)) //Dictionary<String, Int>
print(geregere["HP"]) //Optional(200)

//例2(書式2)
var borongo:Dictionary<String,Int>
borongo = ["HP": 180, "MP": 75, "ATTACK": 125]

print(type(of: borongo)) //Dictionary<String, Int>
print(borongo["MP"]) //Optional(75)

//例3(型推論)
var tiroru = ["HP": 190, "MP": 80, "ATTACK": 130]

print(type(of: tiroru)) // //Dictionary<String, Int>
print(tiroru["ATTACK"]) //Optional(130)

例 3 のように型推論を使って書くこともできます。

辞書型はアクセスすると Optional型 で返ってくる

上の例のアクセスした結果を見ると頭に Optional と付いています。
key を指定してアクセスしていますが、あるか分からない key にアクセスする可能性があるためです。
確実にある key だと分かっている場合であれば強制アンラップ ! でアンラップしてもいいですが、そうでない場合の方が多いと思うので、そういう場合は ?? 演算子を使って nil だった場合に代わりに代入する値を指定した方が安全です。

Optional型アンラップ についてはこちらの記事で紹介しています。

辞書型の変数定義〜アンラップしてアクセスする例

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]
print(suraimu["HP"]) //Optional(100)

まだアンラップ前なので、100 の前に Optional が付いています。
これをアンラップしていきます。

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]
print(suraimu["HP"]) //Optional(100)

//アンラップしてアクセスする
print(suraimu["HP"] ?? "nil") //100

?? 演算子を使って、アクセスした key “HP”nil だった場合は文字列の “nil” を代入します。
これで出力されている値は 100 になったので、
Optional型 から Int型 にアンラップすることができました。

次は key に無いものを指定して、結果が変わるか見てみます。

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]
print(suraimu["HP"]) //Optional(100)

//アンラップしてアクセスする
print(suraimu["HP"] ?? "nil") //100

//keyに無いものを指定してみる
print(suraimu["Defense"] ?? "nil") //nil

key“Defense” と指定して、keynil だった場合は文字列の “nil” を代入します。
結果は “nil” になります。
“Defense” という key は定義していないので( nil なので )、右辺の “nil” が代わりに代入されます。

dictionaryコードimg

要素の順番は保証されない

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]

//要素の順番は保存されない
for i in suraimu.keys {
    print(i)
}

//実行結果
/*
ATTACk
HP
MP
*/

配列型とは異なり、辞書型は要素を順番で管理しないので参照するたびに順番が変わります。そのためインデックス番号が使えません。

要素の追加と変更方法

追加または変更の書式

変数名、または定数名[key] = 値

要素の追加

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]

//要素の追加
suraimu["Defense"] = 10
print(suraimu) //["HP": 100, "ATTACK": 50, "Defense": 10, "MP": 30]

要素の変更

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]

//要素の追加
suraimu["Defense"] = 10
print(suraimu) //["HP": 100, "ATTACK": 50, "Defense": 10, "MP": 30]

//要素の変更
suraimu["Defense"] = 20
print(suraimu) //["HP": 100, "ATTACK": 50, "Defense": 20, "MP": 30]

追加したい key が既存でなければ追加、key がもし既存のものであれば値の変更になります。

要素の削除

removeValue(forKey:) メソッド

辞書型の要素を削除したいときは、removeValue(forKey:) メソッド を使います。

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]

//要素の追加
suraimu["Defense"] = 10
print(suraimu) //["HP": 100, "ATTACK": 50, "Defense": 10, "MP": 30]

//要素の変更
suraimu["Defense"] = 20
print(suraimu) //["HP": 100, "ATTACK": 50, "Defense": 20, "MP": 30]

//要素の削除
//removeValue(forKey:)メソッド
suraimu.removeValue(forKey: "Defense")
print(suraimu) //["HP": 100, "ATTACK": 50, "MP": 30]

removeValue(forKey:) メソッドの引数に削除したい key 指定します。 key が既存であれば削除することができます。

辞書型とよく使うメソッドとプロパティ

.contains メソッド

.contains メソッドは、指定した要素が含まれているかどうかを Bool値 で返すメソッドです。
指定した要素があれば true
指定した要素がなければ false が返ってきます。

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]

//.containsメソッド
print(suraimu.keys.contains("HP")) //true
print(suraimu.keys.contains("Defense")) //false

.keys プロパティ

.keys プロパティは、key の一覧を取得することができるプロパティです。

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]

//.keysプロパティ
print(suraimu.keys) //["HP", "MP", "ATTACK"]

.values プロパティ

.values プロパティは、値の一覧を取得することができるプロパティです。

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]

//.valuesプロパティ
print(suraimu.values) //[100, 30, 50]

.count プロパティ

.count プロパティは、要素の数を取得することができるプロパティです。

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]

//.countプロパティ
print(suraimu.count) //3

.isEmpty プロパティ

.isEmpty プロパティは、要素が含まれているかどうかを Bool値 で取得することができるプロパティです。
何か要素が入っていれば false
何も入ってなければ true を取得します。
要素が 1 つでも入っていれば false 、空であれば true になります。

var suraimu:[String:Int]
suraimu = ["HP": 100, "MP": 30, "ATTACK": 50]

//.isEmptyプロパティ
print(suraimu.isEmpty) //false
dictionaryコードimg2

辞書型 はコレクション型の 1 つで、配列型 もその 1 つです。
この 2 つの他に 集合型( Set ) というコレクション型もあります。
以上 辞書型 についてでした。

次回は 集合型 について書いていこうと思います。

実行環境

version
Xcode 14.2 (14C18)
Swift 5.2.4

公式ドキュメント

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/

【Swift】Optional型

Optional型( オプショナル型 )とは、nilの可能性があるときに使う型です。
Swiftでは値が無い状態のことを、nilと言います。
例えば、必須項目や非必須項目をユーザーに入力してもらう時など、入力されるか分からない項目がある場合などに Optional型を使います。

前回は タプル について書きました。

今回は Optional型 について書いていこうと思います。

Optional型とは

Optional型( オプショナル型 )とは、nil の可能性があるときに使う型です。
Swift では 値が無い 状態のことを、nil と言います。
逆に言うと、Optional型 で宣言している場合のみ nil を代入する事ができます。
例えば、必須項目 や 非必須項目 をユーザーに入力してもらう時など、入力されるか分からない項目がある場合などに Optional型 を使います。

Optional型の書式

//書式1
型名? 

//書式2
Optional<型名>

型名を書いた後に ? を付けます。( 書式 1 )
または
Optional と書いた後 < > の中に型名を書きます。( 書式 2 )

Optional型 の書き方

//例1
var a:Int?
print(type(of: a)) //Optional<Int>

//例2
var b:Optional<Int>
print(type(of: b)) //Optional<Int>

変数 abOptional型 で宣言しています。
この 2 つの変数の型を見てみると、Optional<Int> となっています。これは Optional Int型 という型で、ただの Int型とは別の型になります。なので原則同じ型でなければ演算できないので、Optional Int型Int型 は演算できません。
Optional型 の値を使いたい時は、アンラップ という処理が必要になります。

Int型 に限らず、他の型でも頭に Optional と付いている型の値を使いたい時はアンラップが必要です。

※ 例 1 は初期値を入れずに宣言するとデフォルトで nil が代入されますが、
例 2 は初期値を入れずに宣言しても nil が代入されません。例 2 はどこかで値を代入しないと使うことができないので注意。

//例1
var a:Int?
print(type(of: a)) //Optional<Int>
print(a) //nil

//例2
var b:Optional<Int> = nil //初期値としてnilを代入
print(type(of: b)) //Optional<Int>
print(b) //nil

Optional型を使った例

必須項目と非必須項目がある場合の例

//必須項目と非必須項目がある場合の例
let name:String //必須項目
var mail:String //必須項目

//Optional型で非必須項目の宣言
var phone:String? //非必須項目
var birthday:String? //非必須項目

//必須項目に値を代入
name = "hoge"
mail = "aaa@123.com"

//アクセス
print("\(name)のアカウント情報") //hogeのアカウント情報
print("アカウント名 \(name)") //アカウント名 hoge
print("メールアドレス \(mail)") //メールアドレス aaa@123.com

//非必須項目にアクセス
print("電話番号 \(phone)") //電話番号 nil
print("誕生日 \(birthday)") //誕生日 nil

必須項目と非必須項目に使う変数・定数を宣言して、必須項目にだけ値を代入し全ての変数・定数にアクセスしている例です。
必須項目には値を代入しているのでこれまで通りアクセスできて値もきちんと反映されています。
次に非必須項目ですが、 値を代入していません。
Optional型 で宣言しているので値の代入がされてなくてもデフォルトで初期値に nil が代入されます。
Optional型nil許す型なので、アクセスしたとき値が無くてもエラーやクラッシュではなく nil が返ってきます。
もしこれを Optional型 ではなく String型 などで宣言していれば、値が無い( 初期値が無い )のにアクセスしてしまいエラーやクラッシュする原因になります。

次は非必須項目に値を入れてアクセスしてみます。

//必須項目と非必須項目がある場合の例
let name:String //必須項目
var mail:String //必須項目

//Optional型で非必須項目の宣言
var phone:String?//非必須項目
var birthday:String? //非必須項目

//必須項目に値を代入
name = "hoge"
mail = "aaa@123.com"

//アクセス
print("\(name)のアカウント情報") //hogeのアカウント情報
print("アカウント名 \(name)") //アカウント名 hoge
print("メールアドレス \(mail)") //メールアドレス aaa@123.com

//非必須項目にアクセス
print("電話番号 \(phone)") //電話番号 nil
print("誕生日 \(birthday)") //誕生日 nil

//非必須項目に値を代入
phone = "123-4567-8910"
birthday = "20XX/XX/XX"

//非必須項目に再度アクセス
print("電話番号 \(phone)") //電話番号 Optional("123-4567-8910")
print("誕生日 \(birthday)") //誕生日 Optional("20XX/XX/XX")

非必須項目にも値を代入して再度アクセスしました。
値を入れずにアクセスした時は nil が代入されていましたが、値を入れてアクセスすると nil ではなく、
phone には Optional(“123-4567-8910”)
birthday には Optional(“20XX/XX/XX”)
が代入されています。
String型 の値を代入したのに、この 2 つの変数は Optional String型 で宣言されているので、頭に Optional が付いた状態になっています。
この状態では print関数 で中の値は確認できますが、実際に値を使うことはできません。
使おうとすると、アンラップする必要があります!とエラーが出ます。

~ ~ ~
//非必須項目に値を代入
phone = "123-4567-8910"
birthday = "20XX/XX/XX"

//非必須項目に再度アクセス
print("電話番号 \(phone)") //電話番号 Optional("123-4567-8910")
print("誕生日 \(birthday)") //誕生日 Optional("20XX/XX/XX")

//Optional String型の変数をString型の変数に入れようとするとエラー
var phoneNumber:String = phone //エラー
optionalコードimg

アンラップ

アンラップとは、ラップ( 包装 )の反対語でラップを外すこと。と表現されている事が多いと思います。
Optional型 はラップ( 包装 )された状態で、このラップを外すことをアンラップと言います。

Optional型の値を 非Optional型 の値と同様に扱いたいときは、アンラップ( unwrap )という処理をしなければ使うことができません。

アンラップする方法

  • ?? 演算子( nil合体演算子 )
  • ! 演算子( 強制アンラップ )
  • if-let文 ( オプショナルバインディング )

この他にも guard-let などいくつかアンラップする方法がありますが、今回は上記の 3 種類を使ってアンラップしていきたいと思います。

?? 演算子 ( nil合体演算子 )

?? 演算子を使ったアンラップの書式

Optional型の値 ?? アンラップ失敗または、nilだった場合に入る値(デフォルト値)

左辺の Optional型の値 がアンラップ可能であれば アンラップした値 を代入し、
アンラップ失敗( 値が nil ) であれば右辺の デフォルト値 を代入する演算子です。

~ ~ ~
//非必須項目に値を代入
phone = "123-4567-8910"
birthday = "20XX/XX/XX"

//アクセス
print("電話番号 \(phone)") //電話番号 Optional("123-4567-8910")
print("誕生日 \(birthday)") //誕生日 Optional("20XX/XX/XX")

//アンラップ
//??演算子(nil合体演算子)
var phoneNumber:String = phone ?? "アンラップ失敗"

//アンラップ後に再度アクセス
print("電話番号 \(phoneNumber)") //電話番号 123-4567-8910
print(type(of: phoneNumber)) //String

Optional型変数 phone がアンラップ可能であれば アンラップした値変数 phoneNumber に代入します。
もしアンラップ失敗や、そもそも値が nil であれば右辺の “アンラップ失敗” という文字列を phoneNumber に代入します。

アンラップ前の phone の値には Optional と頭に付いていましたが、
アンラップ後の値を見るとOptional が取れて普通の String型 にアンラップされています。これで String型 の値として扱うことができるようになりました。
アンラップが成功した場合と失敗した場合の両方の処理を書くので、安全にアンラップする方法だと思います。

! 演算子 ( 強制アンラップ )

! 演算子を使ったアンラップの書式

Optional型の値!

Optional型の値 の後に ! を書くことで強制的にアンラップする事ができる演算子です。

~ ~ ~
//非必須項目に値を代入
phone = "123-4567-8910"
birthday = "20XX/XX/XX"

//アクセス
print("電話番号 \(phone)") //電話番号 Optional("123-4567-8910")
print("誕生日 \(birthday)") //誕生日 Optional("20XX/XX/XX")

//アンラップ
//!演算子(強制アンラップ)
var phoneNumber:String = phone!

//アンラップ後に再度アクセス
print("電話番号 \(phoneNumber)") //電話番号 123-4567-8910
print(type(of: phoneNumber)) //String

Optional型変数 phone の後に ! を書いて強制的にアンラップした値を 変数 phoneNumber に代入します。
アンラップ後のアクセスした結果を見るとアンラップされているのが分かります。

※ ただし ! 演算子( 強制アンラップ )は、 Optional型の値nil だった場合に強制アンラップするとプログラムがクラッシュしてしまいます。
値が nil なのに( アンラップの対象が無いのに )強制アンラップしているためです。
なので nil の可能性がある場合、値が入っているか分からない場合は使えません。
確実に値が入っていると分かっている場合に使います。

if-let文 ( オプショナルバインディング )

if-let文 を使ったアンラップの書式

if let 定数名 = Optional型の値 {
    アンラップに成功したらこの中の処理が実行
} else {
    アンラップ失敗したらこの中の処理が実行される
    else文を書いていない場合、このif-let文はスキップ
}

Optional型の値 がアンラップ可能であれば、アンラップした値を定数に代入し、{ } の中の処理が実行されます。
アンラップ失敗( 値が nil )であれば else文{ } の中の処理が実行されます。

else文 を書かなかった場合、アンラップ失敗( 値が nil )であれば if-let文 はスキップされます。

~ ~ ~
//非必須項目に値を代入
phone = "123-4567-8910"
birthday = "20XX/XX/XX"

//アクセス
print("電話番号 \(phone)") //電話番号 Optional("123-4567-8910")
print("誕生日 \(birthday)") //誕生日 Optional("20XX/XX/XX")

//アンラップ
//if-let文(オプショナルバインディング)
if let phoneNumber = phone {
    print(phoneNumber) //電話番号 123-4567-8910
    print(type(of: phoneNumber)) //String
} else {
    print("アンラップ失敗")
}

Optional型変数 phone がアンラップ可能であれば アンラップした値定数phoneNumber に代入し、{ } の中の処理を実行します。
アンラップ失敗( 値が nil )であれば else文{ } の中の処理が実行されます。
このアンラップ方法も アンラップが成功した場合と失敗した場合の両方の処理を書けるので、nil の値をアンラップしようとしてクラッシュするなどのエラー回避につながります。

エラーやクラッシュを回避するためにも、できるだけ強制アンラップは使わず、?? 演算子や if-let文 でアンラップした方が安全だと思います。

お疲れ様でした。
以上 Optional型 についてでした。

次回は while文 について書いていこうと思います。

実行環境

version
Xcode 14.2 (14C18)
Swift 5.2.4

公式ドキュメント

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/