[Swift] Optionalを活用する

# Optionalについて軽くおさらい

### Optionalとは

Swiftではオプショナル型という特殊な型が用意されています。
オプショナル型とは、通常は持っている型とは別に例外的にnilとなってしまう値に対しても定義できる型のことを示します。

例えば、通常は文字列を持っている変数`optionalName`が、ある特殊なケースにおいてnilが代入される場合がある時、変数`optionalName`は`String?`と表現することが出来ます。

```swift
var optionalName: String? = "Brian"

optionalName = nil //OptionalなString型であるため、nilも代入可能 
```

nilの代入が許容されることで、例外的に変数にnilが入った場合でも条件分岐などでnilチェックを行うことでアプリケーションのクラッシュ等を防ぐことが出来ます。

```swift
var optionalName: String? = "Brian"

optionalName = nil //OptionalなString型であるため、nilも代入可能 

if optionalName == nil {
    print("名前にnilが代入されています。")
} else {
    print( optionalName! ) // Optionalな値は、nilでない場合は!をつけて値を開示する必要がある
}
```

### オプショナル束縛構文

上記のように、if文でOptional型がnilかどうかを判別するやり方もありますが、Swiftでは**オプショナル束縛構文**というのも用意されています。

```swift
var optionalName: String? = "Brian"

if let name = optionalName {
    print( name )
}
```

この書き方をすることで、`optionalName`がnilでなければ`name`に値が代入され、条件分岐に入ることが出来ます。
またこの時、`name`に代入されている時点でOptionalな値が開示されているため、`name`を開示する必要はありません。

より詳しいOptionalの解説については、是非こちらをご覧ください。
[SwiftのOptional型を極める](https://qiita.com/koher/items/c6f446bad54442a28bf4)
[詳解Swift 第5版 Chapter4 Optional](https://www.amazon.co.jp/%E8%A9%B3%E8%A7%A3-Swift-%E7%AC%AC5%E7%89%88-%E8%8D%BB%E5%8E%9F-%E5%89%9B%E5%BF%97/dp/481560407X)

# サインイン/サインアウト処理を例に

ではこれがSwiftではどういう時に恩恵が得られるか、アプリのサインイン/サインアウトの処理を例に見てみましょう。

雑ですが、今回はサインインをするための「SigninView」とサインイン後の「ContentView」、そしてサインインしているユーザ情報を保持する「AppState」を以下のように組み合わせたアプリを想定します。
※サインインには、[FirebaseAuthentication](https://firebase.google.com/docs/auth/ios/start?hl=ja)を使います。これについての詳しい説明は割愛します。

![struct1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264906/55de8405-a49b-bb90-9a87-bdc9f24b3527.png)


## サンプルコード

まずはAppStateです。
こちらは、サインインするユーザの情報を保持しており、各Viewから参照されることを想定しているクラスです。

`Session`という構造体は、EnvironmentObjectとして定義しています。

```swift
/// ユーザ情報を格納するリポジトリクラス
class User: Identifiable {
    public var uid: String
    public var email: String?

    init(uid: String, email: String) {
        self.uid = uid
        self.email = email
    }
}

class AppState: ObservableObject {
    
    // 認証状態を管理するための構造体
    struct Session {
        var user: User?
        var handler: AuthStateDidChangeListenerHandle? //サインインしているユーザ情報を保持するオブジェクト
    }
    
    @Published public var session = Session()
}

```

次にSessionに関する処理(サインイン/サインアウト)を担うInteractorクラスです。

```swift
import Firebase

class SessionInteractor {
  
    public var appState: AppState

    /// ViewコンポーネントよりEnvironmentObjectを取得し、クラスプロパティに格納する
    init(appState: AppState) {
        self.appState = appState
    }
  
    /// ユーザのログイン状態が変わるたびに呼び出される処理
  	public func listen() {
        self.appState.session.handler = Auth.auth().addStateDidChangeListener { (auth, user) in
            // guard let文を利用して早期リターン
            guard let user = user else {
                self.appState.session.user = nil
                return
            }            
            // Session型のuserプロパティに追加
            self.appState.session.user = User(
                uid: user.uid,
                email: user.email!
            )
        }
    }
  
    /// サインイン処理
    public func signIn() {
  　    // FirebaseAuthenticationを使ったサインイン処理
      　// FIRAuthオブジェクトのアタッチとかを行っていますが詳細な処理は割愛        
        Auth.auth().signIn(/* [中略]FirebaseAuthenticationを使ったサインイン */)
    }
  
    /// サインアウト処理
    public func signOut() -> Bool {
        do {
            try Auth.auth().signOut()
            self.appState.session.user = nil
            return true
        } catch {
            return false
        }
    }
}
```

次に各Viewのコードです。

```swift
import SwiftUI

struct SignInView: View {
    @EnvironmentObject var appState: AppState
    
    var body: some View {
        VStack() {
            TextField("メールアドレス", text: $email)
            SecureField("パスワード", text: $password)
            Button(action: self.signIn) {
                Text("サインイン")
            }
        }
    }
    
    /// 実際にサインインを行うメソッド
    private func signIn() {
  　    let sessionInteractor = SessionInteractor(appState: self.appState)
        let result = sessionInteractor.signIn()
    }
}

```

```swift
import SwiftUI

struct ContentView: View {
    @EnvironmentObject var appState: AppState
    
    var body: some View {
        VStack() {
            if let user = self.appState.session.user {
                Text("こんにちは、\(user.email!)")
                Button(action: self.signOut) {
                  Text("サインアウト")
                }
            }
        }.onAppear(perform: self.loadUserInfo)
    }
    
  	// サインアウトの処理を呼び出す
    private func signOut() {
        let sessionInteractor = SessionInteractor(appState: self.appState)
        let result = sessionInteractor.signOut()
    }
}

```



## Optionalの定義と開示

AppStateが持っている、`User`オブジェクトが今回の肝となります。

```swift
// 認証状態を管理するための構造体
struct Session {
    var user: User? //記述は省略しますがユーザ情報をプロパティに持つ構造体
    var handler: AuthStateDidChangeListenerHandle? //サインインしているユーザ情報を保持するオブジェクト
}
```

これらはOptionalで定義されていますが、これはサインイン前やサインアウト後などはサインインしているユーザのオブジェクトが`nil`になるためです。
サインインが完了し、Userオブジェクトにユーザ情報が入った後は、Viewの中でそれらの情報を開示しています。

```swift
var body: some View {
    VStack() {
        if let user = self.appState.session.user {
            Text("こんにちは、\(user.email!)") // !で開示して表示
            Button(action: self.signOut) {
              Text("サインアウト")
            }
        }
    }.onAppear(perform: self.loadUserInfo)
}
```

ここで注目したいのが、**オプショナル束縛構文**を使って分岐をしている部分です。

```swift
if let user = self.appState.session.user {
```

これを記述することで、ユーザがサインアウトした後に`User`オブジェクトが`nil`になっても問題ありません。
逆にこれを記述しないと、View側はサインアウト時に代入される`nil`を受け取り、アプリがクラッシュしてしまいます。

```
Fatal error: Unexpectedly found nil while unwrapping an Optional value
```

```swift
try Auth.auth().signOut()
self.appState.session.user = nil // サインアウト時にユーザ情報を剥がしている
```

![struct2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264906/cc817f81-4050-011a-cf51-4b732371152d.png)


# まとめ

今回はサインイン/サインアウトを例に取り上げましたが、モバイルアプリを作る上で複数のViewが同じ値を参照していることが多々あると思います。
参照しているViewが多ければ多いほど、**予期せぬnil**を受け取る可能性があり、アプリのクラッシュを引き起こしてしまいますが、Optional型を活用することで、それらを未然に防ぐことができます。
何でもかんでもOptionalにするべきではありませんが、**nilの代入が想定されるプロパティや変数**に関しては、Optionalを活用しましょう！

# 参考

[iOS で Firebase Authentication を使ってみる](https://firebase.google.com/docs/auth/ios/start?hl=ja)
[SwiftのOptional型を極める](https://qiita.com/koher/items/c6f446bad54442a28bf4)
[詳解Swift 第5版 Chapter4 Optional](https://www.amazon.co.jp/%E8%A9%B3%E8%A7%A3-Swift-%E7%AC%AC5%E7%89%88-%E8%8D%BB%E5%8E%9F-%E5%89%9B%E5%BF%97/dp/481560407X)