Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Property Wrappers With Generic (Optional) User Defaults

With due reference:

https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#user-defaults

We've started to use property wrappers for the UserDefaults, it works seamlessly with non-optional properties.

However, setting nil of an optional property crashes with:

[User Defaults] Attempt to set a non-property-list object as an NSUserDefaults/CFPreferences value for key "someKeyThatWeSet"

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object null for key "someKeyThatWeSet"'

The code below can be tested on Playground directly:

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

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

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

struct C2User {
    @C2AppProperty("userID", defaultValue: nil)
    public static var publicUserID: String?
}

print(C2User.publicUserID)
C2User.publicUserID = "edusta"
print(C2User.publicUserID)
C2User.publicUserID = nil
print(C2User.publicUserID)

Expected:

nil

Optional<"edusta">

nil

Found:

nil

Optional<"edusta">

libc++abi.dylib: terminating with uncaught exception of type NSException

What I've tried so far:

set {
    // Comparing non-optional value of type 'T' to nil always returns false.
    if newValue == nil {
        UserDefaults.standard.removeObject(forKey: combinedKey)
    } else {
        UserDefaults.standard.set(newValue, forKey: combinedKey)
    }
}

What kind of a check is needed here to catch that newValue is nil? Or an Optional<nil>?

like image 393
EDUsta Avatar asked Oct 10 '19 07:10

EDUsta


People also ask

What is a property wrapper?

Property Wrappers. A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property. For example, if you have properties that provide thread-safety checks or store their underlying data in a database, you have to write that code on every property.

How do you use property wrappers?

You can create a Property Wrapper by defining a struct and marking it with the @propertyWrapper attribute. The attribute will require you to add a wrappedValue property to provide a return value on the implementation level.

What is Property wrapper in Swift UI?

Property wrappers were introduced in Swift 5.1 as a way to make it easier to reuse common programming patterns, but since then they have grown to work with local context, function and closure parameters, and more.

What is Wrapper in IOS?

A property wrapper is a generic structure that encapsulates read and write access to the property and adds additional behavior to it. We use it if we need to constrain the available property values, add extra logic to the read/write access (like using databases or user defaults), or add some additional methods.


1 Answers

This code works for me:

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

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

    var wrappedValue: T {
        get {
            guard let value = userDefaults.object(forKey: key) else {
                return defaultValue
            }

            return value as? T ?? defaultValue
        }
        set {
            if let value = newValue as? OptionalProtocol, value.isNil() {
                userDefaults.removeObject(forKey: key)
            } else {
                userDefaults.set(newValue, forKey: key)
            }
        }
    }
}

fileprivate protocol OptionalProtocol {
    func isNil() -> Bool
}

extension Optional : OptionalProtocol {
    func isNil() -> Bool {
        return self == nil
    }
}
like image 162
Michcio Avatar answered Oct 21 '22 18:10

Michcio