Open7

[Swift] オプショナル型

🍤🍤

オプショナル型とは何か。

  • オプショナル型とはデータ型の一つ。すなわち、変数の宣言時に使用するもの。
optional型
var age: Int? 
var name: String!
//ageとnameはオプショナル型で宣言されている。
  • 変数にnilの代入を許すことがポイント。(データのない、空の状態を許容する)
  • swiftでは基本的にnilを許容しない。これはnilが存在することでアプリケーションが落ちてしまうことがあったため。
  • オプショナル型は特別
    -> なぜあえてnilを扱う型が存在する必要があるのか。安全のためにはむしろない方が良いのでは?
    例えばSNSにプロフィールを設定するとして、ユーザー的にあえて登録しないこと(nilの状態)もあり得る。
    こうした時nilを扱う必要が生じる。

Optional
ラップされた値またはnil(値がないこと)のいずれかを表す型。

OptionalのDeclaration
@frozen enum Optional<Wrapped>

Swift’s type system usually shows the wrapped type’s name with a trailing question mark (?) instead of showing the full type name. For example, if a variable has the type Int?, that’s just another way of writing Optional<Int>. The shortened form is preferred for ease of reading and writing code.
Swift の型システムは通常、完全な型名を表示する代わりに、末尾に疑問符 (?) を付けてラップされた型の名前を表示します。たとえば、変数が Int? という型を持っている場合、それは Optional<Int> と書く別の方法です。コードの読み書きを容易にするためには、短縮形が好まれます。

//shortFormとlongFormの型は同じ
let shortForm: Int? = Int("42")
let longForm: Optional<Int> = Int("42")

The Optional type is an enumeration with two cases.

https://developer.apple.com/documentation/swift/optional

🍤🍤

使い方

  • データの最後に ?, ! をつければ使える。
Optional型の宣言
//Optional型の宣言
var hoge: String?
var fuga: Int!

//非Optional型の変数はデータ型の最後には何もつけない
var piyo: String
  • ちなみに、Swiftには三項演算子の?もある。
三項演算子の?とオプショナル型の?
// 下記の例だとisHumanがtrueなら人間、falseならその他が表示される
    var isHuman: Bool = true
    print(isHuman ? "人間": "その他")

// 三項演算子とオプショナルの?の合わせ技
    var monthlyIncomeA: Int? = 200000
    print(monthlyIncome ?? "あなたはニートです。") //200000
    var monthlyIncomeB: Int? = 200000
    print(monthlyIncomeB ?? "あなたはニートです。") //あなたはニートです。
🍤🍤
  • ただの「値」と「Optional(値)」は異なる概念。同列に扱えない。
  • Optionalを通常の値として使うためには、unwrapという操作が必要になる。
  • Optionalは値を包み込むラップ(包み紙) のイメージ
  • オプショナル型は値をOptionalというラップ1枚で包み込んでいる。
  • すると、たとえ中身がない(=nil)状態でも包み紙だけは存在するため、とりあえず扱うことができるようになる。
  • しかし、この便利な包み紙があるために値はラッピングされ、直接扱うことができない。
  • var wrapNum: Int? = 10 の変数 wrapNum はオプショナル型なので値10はラップされ、Optional(10) となる。
  • wrapNum で足し算のような計算をするには、unwrapすることで wrapNum の値をOptional(10)から10に変換する必要がある。
🍤🍤

Forced Unwrapping

  • Forced Unwrapping(強制的unwrap)は Optional(型)を強制的にunwrapする方法
  • どんな値が入っていても関係なく、その値を引き摺り出す。
  • Optional(型の変数の後ろに"!"をつける
Forced Unwrappingで強制的にunwrapする。
var optional: Int? = 10 // Optional型
// そのまま出力
println(optional)
// => Optional(10)

// 強制的アンラップ("!"をつけて)で出力
println(optional!)
// => 10
  • Forced Unwrappingの欠点として、unwrapする対象のOptional型の変数の中身がnilだった場合、errorになりアプリケーションが落ちる 点がある。
  • そのため アンラップをする場合には、必ずラッピングの中身が存在する(=nilではない)ことが保証されていなければいけない
  • また、ここの!オプショナル型の変数宣言の"!"とは別物 である。
🍤🍤

Optional Binding

  • 値がnilだった場合アプリケーションが落ちてしまうというForced Unwrappingの欠点を解決してくれる。
  • 安全という観点からするとなるべくこっちを選択すべき。
  • if-let, guard-let, while文などの条件式と併せて使う。

if let文

  • "nilかどうか"の分岐と同時にunwrapが行われる。
if let
// fruitはnil許容型のオブジェクトとする
if let apple = fruit {
    apple.juice()
} else {
    print("There is no fruit...")
}

// fruitはnil許容型のオブジェクトとする
if fruit != nil {
    let apple = fruit!
    apple.juice()
} else {
    print("There is no fruit...")
}

/*if let apple = fruit 
と
 if fruit != nil {
    let apple = fruit! 
やっていることは同じ。
Optional型のfruitがnilでないか確認した上で、unwrapしたfruitをappleに代入。
*/

guard-let

let optionalString: String? = "aaa"
guard let unwrapString = optionalString else {
    return
}
print(unwrapString) //aaa
//条件が成立しなかった場合にelse節が実行される。
//Optional型のoptionalStringがnilでないか確認した上で、unwrapしたoptionalStringをunwrapStringに代入。

while

while
while let thisNode = currentNode
{
    print(thisNode.value)
    currentNode = thisNode.next
}
//もしcurrentNodeがnilでなかったら、unwrapしてthisNodeに代入する。

使い分けのヒント

  • はっきりした方針がまだ見えていない。
  • if-let文を使うとnil以外の場合の処理を内部で行うことになるが、満たすべき条件が複雑になると、if文のネストも複雑になり、通常の処理がネストの奥深くで行われるようなことになりがち。
  • guard文を適切に使うと、例外的な状況への対応とそこからの脱出をコンパクトに記述できる
  • 結果としてプログラムをわかりやすくすることができる。(気もする)
🍤🍤

Optional Chaining

  • Optional Chainingを使う際はOptional型の変数の後に"?"を付ける。

This is very similar to placing an exclamation point (!) after an optional value to force the unwrapping of its value. The main difference is that optional chaining fails gracefully when the optional is nil, whereas forced unwrapping triggers a runtime error when the optional is nil.

  • Optional型の変数に続けてプロパティ(var score: Int = 0 など)を取得したり、メソッド(インスタンスのプロパティを操作する方法)を呼び出す場合に利用する。
  • オプショナル型の変数?.プロパティ
  • オプショナル型の変数?.メソッド()
  • Optional Bindingと同様に、値がnilかどうかでその後の処理が変わる。
Optional Chaining

class Person {
    var residence: Residence?
//residenceプロパティの中身はnil
}

class Residence {
    var numberOfRooms = 1
}

let kotomi = Person()
//residenceプロパティの中身はnilなので、kotomi もnilになる。
//この状態で `let roomCount =kotomi.residence!.numberOfRooms` とやるとエラーになる。

if let roomCount = kotomi.residence?.numberOfRooms {
    print("ことみの家は \(roomCount) 部屋ある。")
} else {
    print("ことみの家の部屋数がわからない")
}
  • 1階層以上ネストされたプロパティ、メソッド、subscriptsで使える。
  • 脱線するが、subscriptsとはDictionary とか Array で hoge["fuga"] みたいな感じで、要素にアクセスするもののこと。
  • これにより、相互に関連する型の複雑なモデル内のサブプロパティを掘り下げて、それらのサブプロパティ上のプロパティ、メソッド、および添え字にアクセス可能かどうかを確認することができる。
class Person {
    var residence: Residence?
}

class Residence {
    var rooms: [Room] = []
    var numberOfRooms: Int {
        return rooms.count
    }
    subscript(i: Int) -> Room {
        get {
            return rooms[i]
        }
        set {
            rooms[i] = newValue
        }
    }
    func printNumberOfRooms() {
        print("The number of rooms is \(numberOfRooms)")
    }
    var address: Address?
}

🍤🍤
/*これはobjectを受け取ってdataに入れるコード。
 dataに渡す数字は規定値 + 2 したものにしたい。*/

let object = NSUserDefaults.standardUserDefaults().objectForKey("user data")
//objectはあくまでも一時的な箱のイメージ。
var data = object as? [String: Int]
//objectをダウンキャスト。[String: Int]。nilの可能性があるのでOptional型。
//これあれやん、キーバリュー型のデータベースの読み込みコードでは。。[文字列(key): 数字(value)] って感じのを作りたい。
var hp = data?["hp"]
// これあれやん。Subscriptやん。。。
//dataはnilの可能性があるOptional型。
//変数に?を付けた場合は変数がnilの場合に返り値がnilになる。
//もしdataに何か入っていたらそれをhpに代入。

/*いろいろ処理*/

if hp != nil {
    hp = hp! + 2
    data!["hp"] = hp!
} else {
    hp = 2
    data = ["hp": 2]
}
/*もしhpがnilじゃなかったら
 強制unwrapしたhpに2を足す。
 dateを強制アンラップして、hp
 nilだったらhpに2を代入。
 dataに["hp": 2]って感じで入れる。*/

//下記は全く同じ処理。
//Optional Bindingを使うことでForced Unwrappingを回避している。

if var data = data, hp = hp {
    hp += 2
    data["hp"] = hp
} else {
    let defaultHP = 2
    hp = defaultHP
    data = ["hp": defaultHP]
}
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "user data")
//Optional型のdata, hpがnilでないか確認した上で、unwrapしたdata, hpをdata, hpに代入。



let object = NSUserDefaults.standardUserDefaults().objectForKey("user data")
var data: [String: Int]! = object as? [String: Int]
//data: [String: Int]! でdataがオプショナル型であると宣言している。
//もし型キャストに失敗したらnilが返る。
//でもここはまず失敗しない型キャスト。

if data == nil {
    data = ["hp": 0]
}
var hp: Int! = data["hp"]
// hpは確実にIntを入れる。 

/*いろいろ処理*/

hp = hp + 2
data["hp"] = hp
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "user data")

let object = NSUserDefaults.standardUserDefaults().objectForKey("user data")
var data = object as? [String: Int] ?? [String: Int]()
//objectを[String: Int]に型キャスト。
var hp = data["hp"] ?? 0

/*いろいろ処理*/

hp = hp + 2
data["hp"] = hp
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "user data")