Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
943 views
in Technique[技术] by (71.8m points)

swift - Add @Published behaviour for computed property

I am trying to make a ObservableObject that has properties that wrap a UserDefaults variable.

In order to conform to ObservableObject, I need to wrap the properties with @Published. Unfortunately, I cannot apply that to computed properties, as I use for the UserDefaults values.

How could I make it work? What do I have to do to achieve @Published behaviour?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

When Swift is updated to enable nested property wrappers, the way to do this will probably be to create a @UserDefault property wrapper and combine it with @Published.

In the mean time, I think the best way to handle this situation is to implement ObservableObject manually instead of relying on @Published. Something like this:

class ViewModel: ObservableObject {
    let objectWillChange = ObservableObjectPublisher()

    var name: String {
        get {
            UserDefaults.standard.string(forKey: "name") ?? ""
        }
        set {
            objectWillChange.send()
            UserDefaults.standard.set(newValue, forKey: "name")
        }
    }
}

Property wrapper

As I mentioned in the comments, I don't think there is a way to wrap this up in a property wrapper that removes all boilerplate, but this is the best I can come up with:

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

    var objectWillChange: ObservableObjectPublisher?

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

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

class ViewModel: ObservableObject {
    let objectWillChange = ObservableObjectPublisher()

    @PublishedUserDefault(key: "name")
    var name: String = "John"

    init() {
        _name.objectWillChange = objectWillChange
    }
}

You still need to declare objectWillChange and connect it to your property wrapper somehow (I'm doing it in init), but at least the property definition itself it pretty simple.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...