Last active
December 14, 2022 12:55
-
-
Save skyofdwarf/d1e0c63a07a6979a037aca26e4a9d4ab to your computer and use it in GitHub Desktop.
PropertyWrapper 를 이용한 UserDefaults
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UserDefaultManager.swift | |
// UserDefault | |
// | |
// Created by YEONGJUNG KIM on 2022/12/14. | |
// | |
import Foundation | |
/// UserDefaults property wrapper | |
/// | |
/// Optional, Boolean, Int, Float, Array, Dictionary 타입의 경우 명시적으로 기본값을 지정하지 않아도 사용가능하다. | |
/// 그 외 타입은 초기값이 지정되지 않으면 기본값을 명시해야 한다. | |
/// 초기값의 경우 UserDefaults에 값이 추가되기 때문에, 초기값이 필요한지 기본값(UserDefaults에 값이 없는 경우 리턴할)이 필요한지 | |
/// 판단하고 사용해야 한다. | |
/// | |
/// ```swift | |
/// // initial value without default value | |
/// @UserDefaulted("key1") | |
/// var string: String = "initial string value" | |
/// | |
/// // no initial value with default value | |
/// @UserDefaulted("key3", default: "default value") | |
/// var defaultString: String | |
/// | |
/// // optional type with nil default value and without initial value | |
/// @UserDefaulted(key: "key2") | |
/// var ostring: String? | |
/// ``` | |
/// | |
/// ``` | |
/// enum DiaryKey: String, CustomStringConvertible { | |
/// case date, tag | |
/// var description: String { rawValue } | |
/// } | |
/// | |
/// @UserDefaulted(key: DiaryKey.tag) | |
/// var tag: String? | |
/// ``` | |
/// | |
/// - Note: 초기값과 기본값을 구별하여 사용하자 | |
/// 초기값을 지정하면 wrappedValue setter가 호출되어 데이터가 저장되므로 | |
/// 비휘발성 데이터의 경우 디스크/네트워크 등에서 읽어오기 전에 값을 덮어쓰일 수 있으니 주의하자 | |
/// | |
/// - Note: 초기값(wrappedValue)이 지정되지 않은 propertyWrapper의 경우, memberwise initializer(구조체 컴파일러 자동생성 초기화)에서 | |
/// T 가 아닌 Defaulted<T>로 생성된다. | |
@propertyWrapper | |
struct UserDefaulted<T> { | |
let key: String | |
let defaultValue: T | |
var wrappedValue: T { | |
get { | |
guard let object = UserDefaults.standard.object(forKey: key), /* 실제 데이터가 존재하는지 */ | |
let value = object as? T /* 데이터가 T타입인지 */ | |
else { | |
return defaultValue | |
} | |
return value | |
} | |
set { | |
if newValue is ExpressibleByNilLiteral { | |
let mirror = Mirror(reflecting: newValue) | |
if mirror.displayStyle == .optional, mirror.children.isEmpty { | |
UserDefaults.standard.removeObject(forKey: key) | |
} else { | |
UserDefaults.standard.set(newValue, forKey: key) | |
} | |
} else { | |
UserDefaults.standard.set(newValue, forKey: key) | |
} | |
} | |
} | |
/// 주어진 초기값을 UserDefaults에 추가하고 기본값으로도 사용한다. | |
/// | |
/// - Note: 생성 시 UserDefaults에 초기값 추가를 원하는 것이 아니라면 다른 생성자(`init(key:default:)`)를 이용하자. | |
init(wrappedValue: T, key: @autoclosure () -> CustomStringConvertible) { | |
self.key = key().description | |
self.defaultValue = wrappedValue | |
self.wrappedValue = wrappedValue | |
} | |
/// 주어진 초기값과 기본값을 사용하여 UserDefaults을 생성한다. 초기값은 UserDefaults에 바로 추가된다. | |
/// | |
/// - Note: 생성 시 UserDefaults에 초기값 추가를 원하는 것이 아니라면 다른 생성자(`init(key:default:)`)를 이용하자. | |
init(wrappedValue: T, key: @autoclosure () -> CustomStringConvertible, default defaultValue: T) { | |
self.key = key().description | |
self.defaultValue = defaultValue | |
self.wrappedValue = wrappedValue | |
} | |
/// UserDefaults에 키값 추가없이 프로터피를 생성한다, 조회 시 값이 없는 경우 리턴할 기본값을 명시한다. | |
init(key: @autoclosure () -> CustomStringConvertible, default defaultValue: T) { | |
self.key = key().description | |
self.defaultValue = defaultValue | |
} | |
} | |
// MARK: 몇 원형 타입들에 대한 기본값 지원 | |
extension UserDefaulted where T: ExpressibleByNilLiteral { | |
/// Optional 타입 기본값 지원 | |
init(key: @autoclosure () -> CustomStringConvertible, default defaultValue: T = nil) { | |
self.key = key().description | |
self.defaultValue = defaultValue | |
} | |
} | |
extension UserDefaulted where T: ExpressibleByBooleanLiteral { | |
/// Boolean 타입 기본값 지원 | |
init(key: @autoclosure () -> CustomStringConvertible, default defaultValue: T = false) { | |
self.key = key().description | |
self.defaultValue = defaultValue | |
} | |
} | |
extension UserDefaulted where T: BinaryFloatingPoint/*ExpressibleByFloatLiteral*/ { | |
/// Float 타입 기본값 지원 | |
/// | |
/// ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral 사용 시, | |
/// `Ambiguous use of 'init(key:default:)'` 에러를 방지하기 위해 `BinaryFloatingPoint`를 대신 사용한다. | |
init(key: @autoclosure () -> CustomStringConvertible, default defaultValue: T = 0.0) { | |
self.key = key().description | |
self.defaultValue = defaultValue | |
} | |
} | |
extension UserDefaulted where T: ExpressibleByIntegerLiteral { | |
/// Integer 타입 기본값 지원 | |
init(key: @autoclosure () -> CustomStringConvertible, default defaultValue: T = 0) { | |
self.key = key().description | |
self.defaultValue = defaultValue | |
} | |
} | |
extension UserDefaulted where T: ExpressibleByArrayLiteral { | |
/// Aray 타입 기본값 지원 | |
init(key: @autoclosure () -> CustomStringConvertible, default defaultValue: T = []) { | |
self.key = key().description | |
self.defaultValue = defaultValue | |
} | |
} | |
extension UserDefaulted where T: ExpressibleByDictionaryLiteral, T.Key == String { | |
/// [String: Any] 타입 기본값 지원 | |
init(key: @autoclosure () -> CustomStringConvertible, default defaultValue: T = [:]) { | |
self.key = key().description | |
self.defaultValue = defaultValue | |
} | |
} | |
// MARK: CustomStringConvertible | |
extension UserDefaulted: CustomStringConvertible { | |
var description: String { | |
"\(key): '\(wrappedValue)'" | |
} | |
} |
Author
skyofdwarf
commented
Dec 14, 2022
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment