簡體   English   中英

如何在 Swift 中為 UserDefaults 使用 KVO?

[英]How to use KVO for UserDefaults in Swift?

我正在重寫應用程序的一部分,並找到了以下代碼:

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 進行 ovserve,但是如何呢? 當我搜索這個時,我發現了一堆稍微舊的 Objective-C 示例。 觀察 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。

蘋果文檔

當在當前進程之外進行更改或無處不在的默認值更改時,不會發布此通知。 您可以使用鍵值觀察來為感興趣的特定鍵注冊觀察者,以便收到所有更新的通知,無論是在當前進程內部還是外部進行更改。

這是演示 Xcode 項目鏈接,它顯示了如何在 UserDefaults 上設置基於塊的 KVO。

使用可重用類型制作的 Swift 4 版本:

文件: 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)
      }
   }
}

文件: KeyValueObserverResult.swift – 保存 KVO 觀測數據的輔助類型。

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
   }
}

文件: 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

從 iOS 13 開始,現在有一種更酷的方法來做到這一點,使用結合:

import Foundation
import Combine

extension UserDefaults {
    /// Observe UserDefaults for changes at the supplied KeyPath.
    ///
    /// Note: first, extend UserDefaults with an `@objc dynamic` variable
    /// to create a KeyPath.
    ///
    /// - Parameters:
    ///   - keyPath: the KeyPath to observe for changes.
    ///   - handler: closure to run when/if the value changes.
    public func observe<T>(
        _ keyPath: KeyPath<UserDefaults, T>,
        handler: @escaping (T) -> Void)
    {
        let subscriber = Subscribers.Sink<T, Never> { _ in }
            receiveValue: { newValue in
                handler(newValue)
            }
        
        self.publisher(for: keyPath, options: [.initial, .new])
            .subscribe(subscriber)
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM