UserDefaultsにCodable(struct)を保存する

「えー！UserDefaultsにstructを保存できるんですかーーー？」って思ったので調べてみました。

# バージョン

Xcode: v11.3
Swift: v5.3.2

# 対象読者

- UserDefaultsを使ったことのある人
- UserDefaultsにstructを保存できることを知らなかった人

# 実装

```swift:UIViewController.swift
import UIKit

struct Settings: Codable {
	var displayName: String
	var colorTheme: String
}

class ViewController: UIViewController {
	let defaults = UserDefaults.standard
	override func viewDidLoad() {
		super.viewDidLoad()
		let settings = Settings(displayName: "tt", colorTheme: "Red")
		let encoder = JSONEncoder()
		if let encoded = try? encoder.encode(settings) {
			defaults.set(encoded, forKey: "settings")
		}
		if let settings = defaults.object(forKey: "settings") as? Data {
			let decoder = JSONDecoder()
			if let loadedSettings = try? decoder.decode(Settings.self, from: settings) {
				print(loadedSettings)
			}
		}
	}
}

```

こんな感じでいつでも呼び出せるようにしたら便利そうですね。

```swift:UIViewController.swift
import UIKit

struct Settings: Codable {
	var displayName: String
	var colorTheme: String
}

class ViewController: UIViewController {
	override func viewDidLoad() {
		super.viewDidLoad()
		let settings = Settings(displayName: "UserA", colorTheme: "Red")
		let encoder = JSONEncoder()
		if let encoded = try? encoder.encode(settings) {
			UserDefaults.standard.set(encoded, forKey: "settings")
		}
		
		let savedSettings = getSettings()!
		print(savedSettings.colorTheme)
		
	}
	
	func getSettings() -> Settings? {
		if let settings = UserDefaults.standard.object(forKey: "settings") as? Data {
			let decoder = JSONDecoder()
			if let loadedSettings = try? decoder.decode(Settings.self, from: settings) {
				return loadedSettings
			} else {
				print("decode failed")
			}
		} else {
			print("UserDefaults object named settings is not found")
		}
		return nil
	}
}

```

# 解説

## set(_: forKey:)

UserDefaultsにはset(_ value: Any?, forKey: String)が提供されています。「Anyといっても何でも保存できんの？」と思って[公式ドキュメント](https://developer.apple.com/documentation/foundation/userdefaults/1414067-set)を調べてみると

> The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects. For more information, see What is a Property List? in Property List Programming Guide.

NSData, NSString, NSNumber, NSDate, NSArray, NSDictonaryに準拠してるオブジェクトなら保存できるみたいです。



## DataとNSData

上のコードの`encode`という変数の型を調べてみると`Data`になっており、`set(_: forKey:)`は`NSData`しか保存できないのでは？と思って調べてみました。[参考リンク](https://stackoverflow.com/questions/41922505/swift-3-difference-between-data-and-nsdata)によると

> The major change between both is just the name. However, there are some methods that are different. See the link above for these changes in detail.

ということで、基本的には名前にしか差が無いみたいです。

細かい差としては

>There is rather big change. Data is Swift native type and it is a struct, while NSData is an object. Using Data allows us to use one type instead of using NSData for immutable data and NSMutableData for mutable data.

ということで、`Data`を使うことで、immutableのデータには`NSData`をmutableなデータには`NSMutableData`を使う必要がないみたいです。

# 終わりに

今回はUserDefaultsにCodable(struct)を保存する方法を調べてみました。

ユーザー周りの設定を外部のデータベース上に保存しているときとかに結構使えそうですね。

# 参考文献

- [How to load and save a struct in UserDefaults using Codable - free Swift 5.1 example code and tips](https://www.hackingwithswift.com/example-code/system/how-to-load-and-save-a-struct-in-userdefaults-using-codable)
- [NSData | Apple Developer Documentation](https://developer.apple.com/documentation/foundation/nsdata)
- [Data | Apple Developer Documentation](https://developer.apple.com/documentation/foundation/data)
- [set(_:forKey:) | Apple Developer Documentation](https://developer.apple.com/documentation/foundation/userdefaults/1414067-set)
- [Swift 3 difference between Data and NSData - Stack Overflow](https://stackoverflow.com/questions/41922505/swift-3-difference-between-data-and-nsdata)