티스토리 뷰

앱을 만들 때 userDefaults에 중요한 값을 저장하게 되는데

UserDefaults.standard.set(true, forKey: "isPopup")

이러한 방식으로 set을 하고

let isPopup: Bool = UserDefaults.standard.bool(forKey: "isPopup") // true

이런 식으로 불러서 값을 사용하게 됩니다.

이런식으로 사용하다보면 UserDefaults를 각각 파일에서 선언해서 사용하게 되면 분산되어 버리기때문에 관리가 힘들게되고 추적이 어려운 이슈가 생기게 됩니다.

위 이슈를 해결하기 위해 모을 수 있는 공간을 만들고 그 장소에 enum 을 선언하고 저장 프로퍼티를 이용해서 관리를 했었습니다.

// 예시 입니다.
enum Key: String {
    case saveBool
    case saveString
}

var saveBool: Bool {
    get {
        return UserDefaults.standard.bool(forKey: Key.saveBool.rawValue)
    }
    set {
        UserDefaults.standard.set(newValue, forKey: Key.saveBool.rawValue)
    }
}

var saveString: String {
    get {
        return UserDefaults.standard.string(forKey: Key.saveString.rawValue) ?? ""
    }
    set {
        UserDefaults.standard.set(newValue, forKey: Key.saveString.rawValue)
    }
}

이런 식으로 정의하고

print(saveBool) // false 가정 
saveBool = true
print(saveBool) // true 다시 set 하기 전까지는 true
print(saveString) // "1" 가정
saveString = "저장"
print(saveString) // 저장 다시 set 하기 전까지는 저장

이런식으로 사용을 했습니다.

사용되는 값이 많아지면 수많은 저장 프로퍼티 선언으로 인해 가독성이 안좋아지고 키값만 다른 코드 중복이 많아지게 됩니다.

코드 중복을 줄이기 위해서 PropertyWrapper 라는 기능을 Swift 5.1 버전에 추가하게 되었습니다.

애플 WWDC 내용 중에 UserDefaults를 이용한 예시를 이용해 구현해봤습니다.

@propertyWrapper
struct UDWrapper<T> {
    private var storage: UserDefaults = .standard
    private let key: String
    private let defaultValue: T

    init(key: String, defaultValue: T, storage: UserDefaults = .standard) {
        self.key = key
        self.defaultValue = defaultValue
        self.storage = storage
    }

    var wrappedValue: T {
        get { return storage.object(forKey: self.key) as? T ?? self.defaultValue }
        set { storage.set(newValue, forKey: self.key) }
    }
}

으로 구현을 하고

@UDWrapper(key: Key.saveBool.rawValue, defaultValue: false) var saveBool: Bool

print(saveBool) // 값이 true일때
saveBool = false
print(saveBool) // 값이 false로 변경

이런식으로 사용하게 됩니다.

var saveBool: Bool {
    get {
        return UserDefaults.standard.bool(forKey: Key.saveBool.rawValue)
    }
    set {
        UserDefaults.standard.set(newValue, forKey: Key.saveBool.rawValue)
    }
}
에서 

@UDWrapper(key: Key.saveBool.rawValue, defaultValue: false) var saveBool: Bool

으로 코드가 짧아지면서 가독성을 높일 수 있었습니다.

추가적으로 enum, optional, class, sturct에 대해서도 같은 호출로 사용하고 싶었지만ㅠㅠ

따로 분리해서 구현했습니다.

  • Optional
@propertyWrapper
struct UDOptionalWrapper<T> {
    private var storage: UserDefaults = .standard
    private let key: String
    private let defaultValue: T?

    init(key: String, defaultValue: T?, storage: UserDefaults = .standard) {
        self.key = key
        self.defaultValue = defaultValue
        self.storage = storage
    }

    var wrappedValue: T? {
        get { return storage.object(forKey: self.key) as? T ?? self.defaultValue }
        set { storage.set(newValue, forKey: self.key) }
    }
}
  • enum
@propertyWrapper
struct UDEnumWrapper<T: RawRepresentable> {
    private var storage: UserDefaults = .standard
    private let rawValue: String
    private let defaultValue: T?

    init(key: String, defaultValue: T?, storage: UserDefaults = .standard) {
        self.storage = storage
        self.rawValue = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T? {
        get {
            guard let rawValue = storage.object(forKey: rawValue) as? T.RawValue else { return self.defaultValue}
            return T(rawValue: rawValue)
        }
        set {
            storage.set(newValue?.rawValue, forKey: self.rawValue)
        }
    }
}
  • class, struct
@propertyWrapper
struct UDJsonWrapper<T?: Codable> {
    private let storage: UserDefaults
    private let key: String
    private let defaultValue: T?

    init(key: String, defaultValue: T?, storage: UserDefaults = .standard) {
        self.key = key
        self.defaultValue = defaultValue
        self.storage = storage
    }

    var wrappedValue: T? {
        get {
            if let savedData = storage.object(forKey: key) as? Data {
                let decoder = JSONDecoder()
                if let lodedObejct = try? decoder.decode(T.self, from: savedData) {
                    return lodedObejct
                }
            }
            return defaultValue
        }
        set {
            let encoder = JSONEncoder()
            if let encoded = try? encoder.encode(newValue) {
                storage.setValue(encoded, forKey: key)
            }
        }
    }
}

이 외에도 다양한 방법으로 구현가능하니 직접 입맛에 맞게 커스텀해가며 쓰면 될 것같습니다.

참고자료

https://zeddios.tistory.com/1221

https://eunjin3786.tistory.com/472

https://green1229.tistory.com/238

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
글 보관함