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<Value: DatabaseValue>
。它采用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) 中将新元素附加到数组