[iOS] UserDefaultsを使いやすくする方法

# はじめに
UIKitで開発しているアプリでUserDefaultsを使いやすくする方法を調べて比較してみました

# 内容
### UserDefaultsで取得できる値

- `func object(forKey defaultName: String) -> Any?`
指定されたキーに関連付けられたオブジェクト、またはnilキーが見つからなかった場合

- `func url(forKey defaultName: String) -> URL?`
指定されたキーに関連付けられた URL。キーが存在しない場合、このメソッドはnilを返す。

- `func array(forKey defaultName: String) -> [Any]?`
指定されたキーに関連付けられた配列。キーが存在しない場合、またはその値が配列でない場合はnilを返す

- `func dictionary(forKey defaultName: String) -> [String : Any]?`
指定されたキーに関連付けられたディクショナリオブジェクト。キーが存在しない場合、またはその値がディクショナリでない場合はnilを返す

- `func string(forKey defaultName: String) -> String?`
文字列値の場合、指定されたキーに関連付けられた文字列。数値の場合は、数値の文字列値。値が存在しないか、文字列または数値ではない場合にnilを返す

- `func stringArray(forKey defaultName: String) -> [String]?`
文字列オブジェクトの配列。指定された値が存在しない場合、値に配列が含まれていないか、配列に文字列が含まれていない場合がnilを返す

- `func data(forKey defaultName: String) -> Data?`
指定されたキーに関連付けられたデータオブジェクト。キーが存在しない場合、またはその値がデータオブジェクトでない場合はnilを返す

- `func bool(forKey defaultName: String) -> Bool`
指定されたキーに関連付けられたBool値。指定されたキーが存在しない場合、このメソッドはfalseを返す
> このメソッドは、文字列「true」、「YES」、「1」、および数値 1 と 1.0 などの特定の「true」の値をBool値に自動的に変換します。文字列「false」、「NO」、「0」、および数値 0 と 0.0 などの特定の「false」値についても同じことが当てはまり、これらは自動的にブール値に強制変換されます。

- `func integer(forKey defaultName: String) -> Int`
指定されたキーに関連付けられた整数値。指定されたキーが存在しない場合、このメソッドは0を返す
> この方法は、ある値を自動的に等価な整数値に変換するものです（決定できる場合）。Bool値のtrueは1、falseは0になります。 浮動小数点数は、その数より小さい最大の整数になります（例えば、2.67は2になります）。整数を表す文字列は、同等の整数になる（例えば、"123 "は123になる）。

- `func float(forKey defaultName: String) -> Float`
指定されたキーに関連付けられた float 値。キーが存在しない場合、このメソッドは0を返す
> このメソッドは、特定の値を自動的に等価なフロート値（決定できる場合）に強制する。Bool値のtrueは1.0に、falseは0.0になります。整数は、等価なフロート値になる（例えば、2は2.0になる）。浮動小数点数を表す文字列は、同等の浮動小数点数になります（例："123.4 "は123.4）。

- `func double(forKey defaultName: String) -> Double`
指定されたキーに関連付けられた double 値。キーが存在しない場合、このメソッドは0を返す
> このメソッドは、ある値を自動的に等価なDoubleの値（決定できる場合）に強制的に変換します。Bool値のtrueは1.0に、falseは0.0になります。整数は等価なDouble値になる（例えば、2は2.0になる）。浮動小数点数を表す文字列は、等価なDouble値になります（例えば、「123.4」は123.4となります）。


- `func dictionaryRepresentation() -> [String : Any]`
UserDefaultsの値を全て取得

## ①UserDefaultsをExtensionする
単純に`UserDefaults`をExtensionしてコンピューテッドプロパティを定義することでデータの保存と取得が簡単になる

```swift
extension UserDefaults {
    var appLastLaunchDate: Date {
        get {
            return Date(
                timeIntervalSince1970: TimeInterval(integer(forKey: "appLastLaunchDate"))
            )
        }
        set {
            set(newValue.timeIntervalSince1970, forKey: "appLastLaunchDate")
        }
    }
}
```

使う時は

```swift
let lastLaunchDate = UserDefaults.standard.appLastLaunchDate

UserDefaults.standard.appLastLaunchDate = Date()
```

## ②UserDefaultsを扱うclassを作る

ほとんどExtensionする形と同じですが、`UserDefaults`を扱うクラスを作り、その中にコンピューテッドプロパティを定義して使う

```swift
class UserDefaultUtil {
    static let shred = UserDefaultUtil()
    private var userDefaults = UserDefaults.standard

    private init() { }

    var appLastLaunchDate: Date {
        get {
            return Date(
                timeIntervalSince1970: TimeInterval(userDefaults.integer(forKey: "appLastLaunchDate"))
            )
        }
        set {
            userDefaults.set(newValue.timeIntervalSince1970, forKey: "appLastLaunchDate")
        }
    }
}
```

使う時は

```swift
let lastLaunchDate = UserDefaultUtil.shred.appLastLaunchDate

UserDefaultUtil.shred.appLastLaunchDate = Date()
```

## 全部削除させるメソッドを作る
`UserDefaults`は`BundleIdentifier`ごとに端末にplistが作成されるので、以下のようなコードでデータを削除できる

データを全消去することはアカウント削除やテスト実施時など限られると思いますがあると便利ですね

```swift
    func removeAllData() {
        if let domain = Bundle.main.bundleIdentifier {
            userDefaults.removePersistentDomain(forName: domain)
        }
    }
```


# おわりに
すでに実装済みなことが多いと思いますが、個人開発しているアプリで実装してなかったので、少し考えてみました。どちらも大して変わらないですかね

それとたまに`UserDefaults.standard.synchronize()`見かけてましたが、これは[不要](https://developer.apple.com/documentation/foundation/userdefaults/1414005-synchronize)になったのですね

# 参考

https://qiita.com/tanaka-tt/items/ab6c5bc2983a32b11fe3

https://qiita.com/uhooi/items/429cac9b798b9c0937ae