SwiftUI:如何使用 UserDefaults 持久化 @Published 变量?

Posted

技术标签:

【中文标题】SwiftUI:如何使用 UserDefaults 持久化 @Published 变量?【英文标题】:SwiftUI: How to persist @Published variable using UserDefaults? 【发布时间】:2019-12-27 21:57:44 【问题描述】:

我希望保留一个 @Published 变量,以便每次重新启动我的应用程序时它都是相同的。

我想在一个变量上同时使用@UserDefault 和@Published 属性包装器。例如,我需要一个 '@PublishedUserDefault var isLogedIn'。

我有以下propertyWrapper

import Foundation

@propertyWrapper
struct UserDefault<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)
        
    

这是我的设置类

import SwiftUI
import Combine

 class Settings: ObservableObject 

   @Published var isLogedIn : Bool = false

 func doLogin(params:[String:String]) 

        Webservice().login(params: params)  response in

            if let myresponse = response                     
                    self.login = myresponse.login
                    
               
         


我的视图类

struct HomeView : View 
    @EnvironmentObject var settings: Settings
    var body: some View 
        VStack 
            if settings.isLogedIn 
            Text("Loged in")
             else
            Text("Not Loged in")
            
        
    

有没有办法制作一个涵盖持久化和发布的单一属性包装器?

【问题讨论】:

【参考方案1】:
private var cancellables = [String:AnyCancellable]()

extension Published 
    init(wrappedValue defaultValue: Value, key: String) 
        let value = UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
        self.init(initialValue: value)
        cancellables[key] = projectedValue.sink  val in
            UserDefaults.standard.set(val, forKey: key)
        
    


class Settings: ObservableObject 
    @Published(key: "isLogedIn") var isLogedIn = false
    ...

示例:https://youtu.be/TXdAg_YvBNE

所有Codable 类型的版本请查看here

【讨论】:

避免传入objectWillChange 的麻烦,自定义propertyWrapper 意味着使用自定义初始化,它只使用现有存储并通过接收器在更改时存储在集合上。我没有想到。 :-) 我的解决方案比较麻烦,你必须在使用 propertyWrapper 之前在 init() 中设置 objectWillChange 谢谢你,它让它变得更简单了^_^ 谢谢@victor!应该可以采用两种方式,以便用户默认更改更新值。 我对您的回答有一个小问题,在已发布的扩展中添加 Userdefaults 的 Singelton 会导致应用程序保持所有项目都可以访问吗?如果我用钥匙串做同样的事情,它会对性能产生任何影响吗?【参考方案2】:

要保留您的数据,您可以使用@AppStorage 属性包装器。 但是,如果不使用@Published,您的ObservableObject 将不再发布有关更改数据的消息。要解决此问题,只需从属性的 willSet 观察者调用 objectWillChange.send()

class Settings: ObservableObject 
    @AppStorage("Example") var example: Bool = false 
        willSet 
            // Call objectWillChange manually since @AppStorage is not published
            objectWillChange.send()
        
    

【讨论】:

【参考方案3】:

应该可以组成一个新的属性包装器:

该提案的第一次修订版中省略了成分, 因为可以手动组合属性包装器类型。例如, 组合 @A @B 可以实现为 AB 包装器:

@propertyWrapper
struct AB<Value> 
  private var storage: A<B<Value>>

  var wrappedValue: Value 
    get  storage.wrappedValue.wrappedValue 
    set  storage.wrappedValue.wrappedValue = newValue 
  

这种方法的主要好处是它的可预测性: AB决定如何最好地实现A和B的组合,命名它 适当地,并提供其正确的 API 和文档 语义。另一方面,必须手动写出每个 组合是很多样板文件,特别是对于一个功能 主要卖点是消除样板。也是 不幸的是不得不为每个作品发明名字——当我尝试的时候 通过@A @B 组合A 和B,我怎么知道去寻找 手动组合的属性包装器类型 AB?或者也许应该是 学士?

参考:Property WrappersProposal: SE-0258

【讨论】:

【参考方案4】:

您目前无法将 @UserDefault 包裹在 @Published 周围,因为目前不允许这样做。

实现@PublishedUserDefault的方法是将objectWillChange传递到包装器中,并在设置变量之前调用它。

【讨论】:

在Property Wrappers 示例struct Field&lt;Value: DatabaseValue&gt;。它采用String 作为参数。您必须改为传递objectWillChange@Published 的唯一特别之处在于它会在适当的时间调用 objectWillChange.send() @F*** 我在下面发布了我的解决方案【参考方案5】:
struct HomeView : View 
    @StateObject var auth = Auth()
    @AppStorage("username") var username: String = "Anonymous"

    var body: some View 
        VStack 
            if username != "Anonymous" 
                Text("Logged in")
             else
                Text("Not Logged in")
            
        
        .onAppear()
             auth.login()
        
    



import SwiftUI
import Combine

class Auth: ObservableObject 

 func login(params:[String:String]) 

        Webservice().login(params: params)  response in

            if let myresponse = response           
                    UserDefaults.standard.set(myresponse.login, forKey: "username")`
                    
               
         


【讨论】:

以上是关于SwiftUI:如何使用 UserDefaults 持久化 @Published 变量?的主要内容,如果未能解决你的问题,请参考以下文章

使用 @AppStorage 进行设置的 SwiftUI 最佳实践:如何在加载 @AppStorage 之前读取 UserDefaults?

在 SwiftUI 中使用 UserDefaults 进行全局更新

如何在 SwiftUI 中将 Stepper 数据保存到 UserDefaults?

如何在 Userdefaults (SwiftUI) 中将新元素附加到数组

当 UserDefaults 值更改 SwiftUI 时,@FetchRequest 谓词不会更新

SwiftUI 从 UserDefaults.standard 存储和提取字典