λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
🍎 iOS

[iOS/Swift] Property Wrapper λ₯Ό ν™œμš©ν•œ UserDefaults μ΄μš©ν•˜κΈ°

by Danna 2021. 3. 14.
728x90
728x90

μ§€λ‚œ ν¬μŠ€νŒ…μ—μ„œ UserDefaults, Onboarding νŽ˜μ΄μ§€μ— λŒ€ν•΄μ„œ μž‘μ„±ν–ˆμ—ˆκ³ , Onboarding νŽ˜μ΄μ§€λ₯Ό κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄ UserDefaults λ₯Ό μ΄μš©ν–ˆμ—ˆμŠ΅λ‹ˆλ‹€. μŠ€ν„°λ””ν•˜μ‹œλŠ” λΆ„ μ€‘μ—μ„œ Property Wrapper λ₯Ό μ΄μš©ν•˜λ©΄ 효율적으둜 μ‚¬μš©ν•  수 μžˆλ‹€κ³  ν•΄μ„œ 곡뢀λ₯Ό ν–ˆμŠ΅λ‹ˆλ‹€. ✍️ κ°μ‚¬ν•©λ‹ˆλ‹€!

 

πŸ’‘ Property Wrapper λž€? 

Property Wrapper λŠ” Property 에 λŒ€ν•΄ νŠΉμ •ν•œ κΈ°λŠ₯을 μ •μ˜ν•  수 μžˆλŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€. Swift5.1 μ—μ„œ μΆ”κ°€λœ κΈ°λŠ₯이라고 ν•©λ‹ˆλ‹€. 

 

Property wrapper λŠ” Property κ°€ μ €μž₯λ˜λŠ” 방식을 κ΄€λ¦¬ν•˜λŠ” μ½”λ“œμ™€ Property λ₯Ό μ •μ˜ν•˜λŠ” μ½”λ“œ 사이에 뢄리 계측을 μΆ”κ°€ν•΄μ€λ‹ˆλ‹€. μ €λŠ”  Property κ°€ μ •μ˜λ˜κ³ , μ €μž₯되기 전에 Wrapper λ₯Ό κ±°μ³κ°€λŠ” 것이라고 μ΄ν•΄ν–ˆμŠ΅λ‹ˆλ‹€.

 

κΈ°μ‘΄μ—λŠ” Thread-safety check λ₯Ό μ œκ³΅ν•˜κ±°λ‚˜, DB에 μ €μž₯ν•˜λŠ” property κ°€ μ—¬λŸ¬κ°œ ν•„μš”ν•œ 경우, 각각의 property λ§ˆλ‹€ μž‘μ„±ν•΄μ•Ό ν–ˆμ§€λ§Œ, Property wrapper λ₯Ό μ΄μš©ν•˜λ©΄ ν•œ 번만 μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 

 

DB 에 μ €μž₯ν•˜λŠ” λ“±μ˜ management code λ₯Ό Property wrapper λ₯Ό μ •μ˜ν•˜λŠ” λΆ€λΆ„μ—μ„œ ν•œλ²ˆλ§Œ μž‘μ„±ν•˜λ©΄ λ©λ‹ˆλ‹€. 이후에 μ—¬λŸ¬ property μ—μ„œ Property wrapper λ₯Ό λͺ…μ‹œν•΄ μž¬μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€! 

 

βœ”οΈŽ Property Wrapper μ‚¬μš©ν•˜κΈ°

Property wrapperλ₯Ό μ •μ˜ν•˜κΈ° μœ„ν•΄μ„œλŠ”

 

  1. structure, enumeration ν˜Ήμ€ class λ₯Ό μ •μ˜ν•˜κ³  @propertyWrapper λ₯Ό λͺ…μ‹œν•΄μ€λ‹ˆλ‹€.
  2. μ‹€μ œ μ €μž₯λ˜λŠ” 값을 wrappedValue μ†μ„±μœΌλ‘œ μ •μ˜ν•©λ‹ˆλ‹€. * ν•„μˆ˜λ‘œ κ΅¬ν˜„ν•΄μ•Όν•©λ‹ˆλ‹€.

Property wrapper 의 ν˜•νƒœλŠ” computed property 의 getter, setter λ₯Ό μ„€μ •ν•˜λŠ” λͺ¨μŠ΅κ³Ό κ°™μŠ΅λ‹ˆλ‹€. setter μ—μ„œ μƒˆλ‘­κ²Œ λ“€μ–΄μ˜€λŠ” 값을 newValue 라고 ν•©λ‹ˆλ‹€. Property warpper λŠ” λ°˜λ³΅λ˜λŠ” computed property λ₯Ό ν•˜λ‚˜μ˜ struct 둜 μ •μ˜ν•˜λŠ” 역할을 ν•œλ‹€κ³  생각할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ•„λž˜ μ˜ˆμ œμ—μ„œ TwelveOrLess κ΅¬μ‘°μ²΄λŠ” number κ°€ 항상 12보닀 μž‘κ±°λ‚˜ κ°™μŒμ„ 보μž₯ν•©λ‹ˆλ‹€. wrappedValue λ₯Ό μ‚΄νŽ΄λ³΄λ©΄, setter μ—μ„œ 12보닀 큰 μˆ˜λŠ” 12κ°€ λŒ€μž…λ˜λ„λ‘ κ΅¬ν˜„λ˜μ–΄μžˆκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

 

@propertyWrapper
struct TwelveOrLess {
    private var number: Int
    init() { self.number = 0 }
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}

 

μœ„μ˜ ꡬ쑰체λ₯Ό μ΄μš©ν•œ μ˜ˆμ œλŠ” μ•„λž˜ μ½”λ“œμž…λ‹ˆλ‹€. SmallRectangle ꡬ쑰체에 height, width 속성이 있고, @TwelveOrLess (wrapper) λ₯Ό μž‘μ„±ν•΄μ€ŒμœΌλ‘œ Property wrapper λ₯Ό μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 

 

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// Prints "0"

rectangle.height = 10
print(rectangle.height)
// Prints "10"

rectangle.height = 24
print(rectangle.height)
// Prints "12"

 

βœ”οΈŽ UserDefaults μ½”λ“œμ— Property Wrapper λ₯Ό μ μš©ν•˜κΈ°

 

쑰언을 λ“£κ³  ν”„λ‘œμ νŠΈμ˜ μ½”λ“œλ₯Ό λ³΄λ‹ˆ, UserDefaults κ΄€λ ¨ μ½”λ“œλ“€μ΄ key κ°’λ§Œ λ‹€λ₯΄κ³  μ „λΆ€ 같은 μ½”λ“œλ₯Ό λ°˜λ³΅ν•΄ μž‘μ„±ν•˜κ³  μžˆμ—ˆμŠ΅λ‹ˆλ‹€. Propery Wrapper λ₯Ό μ΄μš©ν•΄ λ°˜λ³΅λ˜λŠ” μ½”λ“œλ₯Ό 쀄여보도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€.

 

  1. @propertyWrapper λ₯Ό λͺ…μ‹œν•œ ꡬ쑰체 UserDefault λ₯Ό μ •μ˜ν•΄μ€λ‹ˆλ‹€.
  2. ꡬ쑰체의 property λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€.
  3. property 의 μ΄ˆκΈ°ν™”λ₯Ό μœ„ν•΄ init ν•¨μˆ˜λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€.
  4.  wrappedValue λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€. * ν•„μˆ˜ κ΅¬ν˜„ property μž…λ‹ˆλ‹€.

wrappedValue 의 getter λŠ” UserDefaults λ₯Ό 톡해 object λ₯Ό 가져와 λ°˜ν™˜ν•˜κ³ , setter λŠ” UserDefaults λ₯Ό 톡해 값을 μ €μž₯ν•©λ‹ˆλ‹€.

 

참고둜, μ—¬κΈ°μ„œ <T> λŠ” νƒ€μž… 인자(Type Parameters)둜 ν•¨μˆ˜μ˜ μΈμžλ‚˜ λ°˜ν™˜ νƒ€μž…μœΌλ‘œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. νƒ€μž… μΈμžλŠ” ν•¨μˆ˜κ°€ 호좜될 λ•Œ μ‹€μ œ νƒ€μž…μœΌλ‘œ λŒ€μ²΄λ˜λ„λ‘ ν‘œμ‹œλ©λ‹ˆλ‹€. 

 

@propertyWrapper
struct UserDefault<T> {
    private let key: String
    private let defaultValue: T

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

    var wrappedValue: T {
        get {
            // Read value from UserDefaults
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            // Set value to UserDefaults
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

 

μ •μ˜ν•œ UserDefault ꡬ쑰체λ₯Ό μ΄μš©ν•˜κΈ° μœ„ν•œ μ½”λ“œμž…λ‹ˆλ‹€.  User κ΅¬μ‘°μ²΄μ—μ„œλŠ” @UserDefault wrapperκ°€ λͺ…μ‹œλœ isApplicationLaunch property λ₯Ό κ°–μŠ΅λ‹ˆλ‹€. property 의 μ •μ˜μ™€ ν•¨κ»˜ wrapper μ΄ˆκΈ°ν™”λ„ μ§„ν–‰ν–ˆμŠ΅λ‹ˆλ‹€. 앱이 처음 μ‹€ν–‰λ˜λŠ” κ²½μš°μ— μ •μ˜λ κ²ƒμ΄λ―€λ‘œ μ΄ˆκΈ°κ°’μ„ true 둜 섀정을 ν•΄μ€¬μŠ΅λ‹ˆλ‹€. 

 

key 값을 μ§€μ •ν•˜κΈ° μœ„ν•΄μ„œ κΈ°μ‘΄μ—λŠ” "~~" ν˜•νƒœλ‘œ λ°”λ‘œ μž‘μ„±ν–ˆλŠ”λ°, μŠ€ν„°λ””μ—μ„œ 참고둜 보내주신 μ½”λ“œμ— enum 이 있길래 μ΄μš©μ„ ν•΄λ΄€μŠ΅λ‹ˆλ‹€.  ν•΄λ‹Ή μ½”λ“œμ—μ„œλŠ” constant string 을 ν‘œν˜„ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœ 것 κ°™μŠ΅λ‹ˆλ‹€.

 

struct User {
    @UserDefault(key: keyEnum.isAppFirstLaunch.rawValue, defaultValue: true)
    static var isAppFirstLaunch: Bool
}

enum keyEnum: String {
    case isAppFirstLaunch = "isAppFirstLaunch"
}

 

μ˜¨λ³΄λ”© νŽ˜μ΄μ§€ 예제의 SceneDelegate extension μ½”λ“œμ—μ„œ μ•„λž˜ ν•¨μˆ˜λ§Œ μˆ˜μ •ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€. if ꡬ문과 property 값을 μ„€μ •ν•˜λŠ” λΆ€λΆ„μ˜ μ½”λ“œκ°€ λ‹¬λΌμ‘ŒμŠ΅λ‹ˆλ‹€ πŸ™‚ κΈ°μ‘΄κ³Ό λ™μΌν•˜κ²Œ μž‘λ™ν•˜λŠ” 것을 ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.

 

λ‹¨μˆœνžˆ property 에 값을 λŒ€μž…ν•˜λŠ” μ½”λ“œλ§ŒμœΌλ‘œ DB 에 값을 μ €μž₯ν•  수 있게 λ˜μ—ˆλ„€μš”. DB μ—μ„œ 값을 κ°€μ Έμ˜€λŠ” 것은 λ”μš± κ°„λ‹¨ν•΄μ‘Œκ³ μš”. 

 

extension SceneDelegate {
    private func setRootViewController(_ scene: UIScene){
        if User.isAppFirstLaunch {
            User.isAppFirstLaunch = false
            setRootViewController(scene, name: "Onboarding",
                                  identifier: "OnboardingViewController")
        } else {
            setRootViewController(scene, name: "Main",
                                  identifier: "TodoViewController")
        }
    }
    // ...
 }

 

μ œκ°€ μž‘μ„±ν–ˆλ˜ μ˜ˆμ œλŠ” UserDefaults 에 ν•˜λ‚˜μ˜ κ°’λ§Œ μ €μž₯ν•˜κ³  μžˆμ§€λ§Œ, 더 λ§Žμ€ 값을 μ €μž₯ν•œλ‹€λ©΄ μœ μš©ν• κ±°λΌκ³  μ˜ˆμƒλ©λ‹ˆλ‹€. πŸ‘


μ°Έκ³  λ¬Έμ„œ 및 링크

 

κ΄€λ ¨λœ λ‚΄μš©

728x90
728x90