我正在重写应用程序的一部分,并找到了这段代码:
fileprivate let defaults = UserDefaults.standard
func storeValue(_ value: AnyObject, forKey key:String) {
defaults.set(value, forKey: key)
defaults.synchronize()
NotificationCenter.default.post(name: Notification.Name(rawValue: "persistanceServiceValueChangedNotification"), object: key)
}
func getValueForKey(_ key:String, defaultValue:AnyObject? = nil) -> AnyObject? {
return defaults.object(forKey: key) as AnyObject? ?? defaultValue
}
当CMD点击行defaults.synchronize()
时,我看到synchronize
计划被弃用。这是在代码中写的:
/*!
-synchronize is deprecated and will be marked with the NS_DEPRECATED macro in a future release.
-synchronize blocks the calling thread until all in-progress set operations have completed. This is no longer necessary. Replacements for previous uses of -synchronize depend on what the intent of calling synchronize was. If you synchronized...
- ...before reading in order to fetch updated values: remove the synchronize call
- ...after writing in order to notify another program to read: the other program can use KVO to observe the default without needing to notify
- ...before exiting in a non-app (command line tool, agent, or daemon) process: call CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication)
- ...for any other reason: remove the synchronize call
*/
据我所知,我的情况下的用法符合第二种描述:写作后同步,以通知其他人。
它建议使用KVO观察,但如何?当我搜索这个时,我发现了一堆稍微旧的Objective-C-examples。观察UserDefaults的最佳做法是什么?
从iOS 11 + Swift 4开始,推荐的方式(根据SwiftLint)使用基于块的KVO API。
例:
假设我的用户默认值中存储了一个整数值,它被称为greetingsCount
。
首先,我需要扩展UserDefaults
:
extension UserDefaults {
@objc dynamic var greetingsCount: Int {
return integer(forKey: "greetingsCount")
}
}
这允许我们稍后定义观察的关键路径,如下所示:
var observer: NSKeyValueObservation?
init() {
observer = UserDefaults.standard.observe(\.greetingsCount, options: [.initial, .new], changeHandler: { (defaults, change) in
// your change logic here
})
}
永远不要忘记清理:
deinit {
observer?.invalidate()
}
来自David Smith http://dscoder.com/defaults.html https://twitter.com/catfish_man/status/674727133017587712的博客
如果一个进程设置了一个共享默认值,然后通知另一个进程读取它,那么你可能处于极少数剩余情况之一,调用-synchronize方法是有用的:-synchronize充当“障碍”,在那里它提供了一个保证,一旦它返回,任何其他读取该默认值的进程将看到新值而不是旧值。
对于在iOS 9.3及更高版本/ macOS Sierra及更高版本上运行的应用程序,即使在这种情况下也不需要(或推荐)-synchronize,因为默认值的键值观察现在在进程之间起作用,因此读取过程可以直接查看要改变的价值。因此,在这些操作系统上运行的应用程序通常不应调用同步。
因此,在大多数情况下,您不需要设置为同步调用。它由KVO自动处理。
要执行此操作,您需要在处理persistanceServiceValueChangedNotification
通知的类中添加观察者。假设您正在设置名为“myKey”的密钥
在你的班级添加观察者可能是viewDidLoad
等
UserDefaults.standard.addObserver(self, forKeyPath: "myKey", options: NSKeyValueObservingOptions.new, context: nil)
处理观察者
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
//do your changes with for key
}
同时删除deinit
中的观察者
对于将来寻找答案的任何人,只有在同一过程中进行更改时才会发布didChangeNotification
,如果您希望收到所有更新,无论过程使用KVO如何。
在当前进程之外进行更改或无处不在的默认值更改时,不会发布此通知。无论是在当前进程内部还是外部进行更改,您都可以使用键值观察来注册感兴趣的特定键的观察者,以便获得所有更新的通知。
这是一个link to demo Xcode project,它显示了如何在UserDefaults上设置基于块的KVO。
使用可重用类型制作的Swift 4版本:
File:KeyValueObserver.swift - 通用可重用KVO观察者(对于不能使用纯Swift可观察量的情况)。
public final class KeyValueObserver<ValueType: Any>: NSObject, Observable {
public typealias ChangeCallback = (KeyValueObserverResult<ValueType>) -> Void
private var context = 0 // Value don't reaaly matter. Only address is important.
private var object: NSObject
private var keyPath: String
private var callback: ChangeCallback
public var isSuspended = false
public init(object: NSObject, keyPath: String, options: NSKeyValueObservingOptions = .new,
callback: @escaping ChangeCallback) {
self.object = object
self.keyPath = keyPath
self.callback = callback
super.init()
object.addObserver(self, forKeyPath: keyPath, options: options, context: &context)
}
deinit {
dispose()
}
public func dispose() {
object.removeObserver(self, forKeyPath: keyPath, context: &context)
}
public static func observeNew<T>(object: NSObject, keyPath: String,
callback: @escaping (T) -> Void) -> Observable {
let observer = KeyValueObserver<T>(object: object, keyPath: keyPath, options: .new) { result in
if let value = result.valueNew {
callback(value)
}
}
return observer
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.context && keyPath == self.keyPath {
if !isSuspended, let change = change, let result = KeyValueObserverResult<ValueType>(change: change) {
callback(result)
}
} else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
File:KeyValueObserverResult.swift - 保持KVO观测数据的Helper类型。
public struct KeyValueObserverResult<T: Any> {
public private(set) var change: [NSKeyValueChangeKey: Any]
public private(set) var kind: NSKeyValueChange
init?(change: [NSKeyValueChangeKey: Any]) {
self.change = change
guard
let changeKindNumberValue = change[.kindKey] as? NSNumber,
let changeKindEnumValue = NSKeyValueChange(rawValue: changeKindNumberValue.uintValue) else {
return nil
}
kind = changeKindEnumValue
}
// MARK: -
public var valueNew: T? {
return change[.newKey] as? T
}
public var valueOld: T? {
return change[.oldKey] as? T
}
var isPrior: Bool {
return (change[.notificationIsPriorKey] as? NSNumber)?.boolValue ?? false
}
var indexes: NSIndexSet? {
return change[.indexesKey] as? NSIndexSet
}
}
File:Observable.swift - 暂停/恢复和处置观察者的协议。
public protocol Observable {
var isSuspended: Bool { get set }
func dispose()
}
extension Array where Element == Observable {
public func suspend() {
forEach {
var observer = $0
observer.isSuspended = true
}
}
public func resume() {
forEach {
var observer = $0
observer.isSuspended = false
}
}
}
文件:UserDefaults.swift - 用户默认值的便捷扩展。
extension UserDefaults {
public func observe<T: Any>(key: String, callback: @escaping (T) -> Void) -> Observable {
let result = KeyValueObserver<T>.observeNew(object: self, keyPath: key) {
callback($0)
}
return result
}
public func observeString(key: String, callback: @escaping (String) -> Void) -> Observable {
return observe(key: key, callback: callback)
}
}
用法:
class MyClass {
private var observables: [Observable] = []
// IMPORTANT: DON'T use DOT `.` in key.
// DOT `.` used to define `KeyPath` and this is what we don't need here.
private let key = "app-some:test_key"
func setupHandlers() {
observables.append(UserDefaults.standard.observeString(key: key) {
print($0) // Will print `AAA` and then `BBB`.
})
}
func doSomething() {
UserDefaults.standard.set("AAA", forKey: key)
UserDefaults.standard.set("BBB", forKey: key)
}
}
从命令行更新默认值:
# Running shell command below while sample code above is running will print `CCC`
defaults write com.my.bundleID app-some:test_key CCC