SwiftでUserDefaultsを簡単&安全に管理する方法
はじめに
iOSアプリ開発では、UserDefaults を使用してユーザー設定やアプリの状態を保存することが一般的です。
余談ですが、UserDefaultsの情報は.plistという拡張子をもったファイルに保存されます。実際に plistに保存されるのはこんな感じのXMLデータになります。
<plist version="1.0">
<dict>
<key>isDarkMode</key>
<true/>
<key>username</key>
<string>zkpn</string>
</dict>
</plist>
UserDefaultsの基本的な使い方
まず、UserDefaultsの基本的な操作方法をおさらいします。
let defaults = UserDefaults.standard
// 値を保存
defaults.set(true, forKey: "isDarkMode")
// 値を取得
let isDarkMode = defaults.bool(forKey: "isDarkMode")
// 値を削除
defaults.removeObject(forKey: "isDarkMode")
この方法でも十分動作しますが、キーのハードコーディングや型安全性の欠如 が問題となります。
プロパティラッパーを使ってUserDefaultsを簡単に扱う
Swiftの PropertyWrapper を利用すると、UserDefaultsの操作を簡潔に記述できます。プロパティラッパーについては公式がわかりやすいので興味ある方はぜひ。
UserDefault プロパティラッパーの作成
@propertyWrapper
struct UserDefault<T> {
private let key: String
private let defaultValue: T
private let storage: UserDefaults
init(_ key: String, defaultValue: T, storage: UserDefaults = .standard) {
self.key = key
self.defaultValue = defaultValue
self.storage = storage
}
var wrappedValue: T {
get {
storage.object(forKey: key) as? T ?? defaultValue
}
set {
storage.set(newValue, forKey: key)
}
}
}
この @UserDefault
を利用すれば、UserDefaultsの値を簡潔に管理できます。
使用例
class AppSettings {
@UserDefault("isDarkMode", defaultValue: false)
static var isDarkMode: Bool
@UserDefault("username", defaultValue: "")
static var username: String
}
// 値を設定
AppSettings.isDarkMode = true
AppSettings.username = "zkpn"
// 値を取得
print(AppSettings.isDarkMode) // true
print(AppSettings.username) // "zkpn"
この方法を使うと、UserDefaultsのキー管理を簡略化し、型安全にアクセスできるようになります。
UserDefaultsの管理をプロトコルで統一する
プロトコルを利用すると、UserDefaultsの管理を統一的に行う ことができます。
UserDefaultsStorageProtocol の定義
protocol UserDefaultsStorageProtocol {
var isDarkMode: Bool { get set }
var username: String { get set }
}
UserDefaultsStorage の実装
final class UserDefaultsStorage: UserDefaultsStorageProtocol {
@UserDefault("isDarkMode", defaultValue: false)
var isDarkMode: Bool
@UserDefault("username", defaultValue: "")
var username: String
}
この方法を採用すると、
- UserDefaultsのアクセスが統一される
-
UserDefaultsStorageProtocol
に準拠することで、テスト時にモックデータと差し替え可能
といったメリットがあります。
UserDefaultsにCodableなデータを保存する
通常、UserDefaultsには String
, Int
, Bool
などのプリミティブ型しか保存できません。しかし、Codable を活用すると、複雑なデータ構造も保存できます。
Codableを使った保存と取得
struct UserProfile: Codable {
let name: String
let age: Int
}
@propertyWrapper
struct CodableUserDefault<T: Codable> {
private let key: String
private let defaultValue: T
private let storage: UserDefaults
init(_ key: String, defaultValue: T, storage: UserDefaults = .standard) {
self.key = key
self.defaultValue = defaultValue
self.storage = storage
}
var wrappedValue: T {
get {
if let data = storage.data(forKey: key),
let value = try? JSONDecoder().decode(T.self, from: data) {
return value
}
return defaultValue
}
set {
if let data = try? JSONEncoder().encode(newValue) {
storage.set(data, forKey: key)
}
}
}
}
使用例
class UserManager {
@CodableUserDefault("userProfile", defaultValue: UserProfile(name: "Guest", age: 0))
static var userProfile: UserProfile
}
// データを保存
UserManager.userProfile = UserProfile(name: "zkpn", age: 30)
// データを取得
print(UserManager.userProfile.name) // "zkpn"
print(UserManager.userProfile.age) // 30
これにより、Codable
に準拠したデータを簡単にUserDefaultsに保存・取得できるようになります。