UserDefaults
출처 : https://swifter.kr/
1)인스턴스 생성
UserDefaults.standard
2)저장
UserDefaults.standard.set(true, forKey: "boolKeyName")
UserDefaults.standard.set(1, forKey: "integerKeyName")
저장관련 메소드는 set(_: forKey:)구조로 되어 있다. 컴파일러가 첫번째 파라미터의 형을 유추하여 알맞은 메소드가 호출하는 구조이다.
3)읽기
UserDefaults.standard.bool(forKey: "boolKeyName")
UserDefaults.standard.integer(forKey: "integerKeyName")
4)삭제
UserDefaults.standard.removeObject(forKey: "boolKeyName")
4)존재 확인
if UserDefaults.standard.object(forKey: "boolKeyName") != nil { ~ }
4)전체 데이터 삭제
func deleteUserDefaults {
let appDomain = Bundle.main.bundleIdentifier
UserDefaults.standard.removePersistentDomain(forName: appDomain!)
}
4)데이타 종류
let userDefaults = UserDefaults.standard
userDefaults.set(5, forKey: "number")
userDefaults.set(true, forKey: "Bool")
userDefaults.set("Batman", forKey: "John")
userDefaults.set(NSData(), forKey: "data")
let food = ["Pizza", "Burgger", "Coke"]
userDefaults.set(food, forKey: "junkFood")
//호출 데이타
let numberValue = userDefaults.integer(forKey: "number")
print("Number is = \(numberValue)")
let boolValue = userDefaults.bool(forKey: "Bool")
print("bool value is = \(boolValue)")
let nameValue = userDefaults.object(forKey: "John") as! String
print("Name is = \(nameValue)")
let data = userDefaults.object(forKey: "data") as! NSData
print("Data value = \(data)")
let foodArray = userDefaults.object(forKey: "junkFood") as? [String] ?? [String]()
print("Food item count \(foodArray.count)")
print("First food item is \(foodArray[0])")
4)문제점
struct Constants {
static let telKey = "tel"
}
// 전화번호
let tel = "010-1111-1111"
// 저장
UserDefaults.standard.set(tel, forKey: Constants.telKey)
// 읽기
UserDefaults.standard.string(forKey: Constants.telKey)
보통 UserDefaults의 키명은 저장 및 읽을 때 여러번 사용될 가능성이 높기 때문에 상수로 하는 것이 일반적이다. 그러나 이 방법은 문자열 상수를 사용하고 있기 때문에 잘못 코딩하여 버그를 유발할 수 있는 가능성이 높다. 그래서 추천하는 방법은 잘못 코딩하는 실수를 방지하기 위해 정수로 정의하는 문자열을 Enum으로 변경하는 것이 좀더 안심이 된다.
struct Constants {
enum User: String {
case tel
}
}
// 전화번호
let tel = "010-1111-1111"
// 저장
UserDefaults.standard.set(tel, forKey: Constants.User.tel.rawValue)
// 읽기
UserDefaults.standard.string(forKey: Constants.User.tel.rawValue)
enum은 열거자에 String형의 Raw값 tel을 지정할 수 있어 문자열을 직접 tel이라고 작성할 필요가 없다.
그럴지만 위와 같은 방식으로 잘못 코딩하는 오류를 줄일 수 있지만 키명에 Constants.User.tel.rawValue라고 하는 것은 비효율적이고 Swift언어를 사용하는 개발자라면 프로토콜 확장을 통해 간결하게 처리할 수 있다.
protocol KeyNamespaceable {
func namespaced<T : RawRepresentable>(_ key: T) -> String
}
extension KeyNamespaceable {
func namespaced<T: RawRepresentable>(_ key: T) -> String {
return "\(Self.self).\(key.rawValue)"
}
}
protocol StringDefaultSettable : KeyNamespaceable {
associatedtype StringKey : RawRepresentable
}
extension StringDefaultSettable where StringKey.RawValue == String {
func set(_ value: String, forKey key: StringKey) {
let key = namespaced(key)
UserDefaults.standard.set(value, forKey: key)
}
@discardableResult
func string(forKey key: StringKey) -> String? {
let key = namespaced(key)
return UserDefaults.standard.string(forKey: key)
}
}
extension UserDefaults : StringDefaultSettable {
enum StringKey : String {
case tel
}
}
사용방법은 아래와 같다.
let tel = "010-1111-1111" // 전화번호
// 저장
UserDefaults.standard.set(tel, forKey: .tel) //키명: UserDefaults.tel
// 읽기
UserDefaults.standard.string(forKey: .tel) //키명: UserDefaults.tel
UserDefaults의 저장 및 읽어오는 코드가 더 간결했졌다. KeyNamespaceable은 UserDefaults 키명의 충돌을 막기 위한 프로토콜이다. 이를 적용하여 키명은 UserDefaults.tel되지만 알맞지 않은 네임스페이스가 포함되어 있지 않는 단순한 tel이 되어 버리기 때문에 충돌문제가 발생할 가능성이높다.
StringDefaultSettable은 UserDefaults에 String형 값을 저장해석하는 프로토콜이다. 이 확장 메소드는 set(_: forKey:), string(forKey:)는 UserDefaults API와 동일하다.
실제 프로젝트에서는 예로 tel 전화번호와 같은 정보는 모델클래스 속성으로 구성하는 경우가 많다고 본다. 그렇기 때문에 StringDefaultSettable 프로토콜 적합성을 UserDefaults에 한정하고 있지 않고 있때문에 다음과같은 모델클래스에도 사용이 가능하다.
struct User {
let name: String
let tel: String
}
extension User: StringDefaultSettable {
enum StringKey: String {
case name
case tel
}
}
UserDefaults에서 tel을 가지고 아무 전화번호나 소스코드에서 읽을 수 없지만 User에서 tel을 가지면 사용자 전화번호라는 것이 더 명확해 진다.