0

I have written a custom PropertyWrapper, that tries to wrap UserDefaults while also giving them the same behaviour as a @Published variable. It almost works, except that the ObservableObject does not propagate the changes without observing the UserDefaults themselves.

I cannot pass a objectWillChange ref to the @Setting init, as self is not available during Settings.init...

I wonder how @Published does that..

import Combine
import Foundation

class Settings: ObservableObject {

// Trying to avoid this:
/////////////////////////////////////////////
  let objectWillChange = PassthroughSubject<Void, Never>()
  private var didChangeCancellable: AnyCancellable?
  private init(){
    didChangeCancellable = NotificationCenter.default
      .publisher(for: UserDefaults.didChangeNotification)
      .map { _ in () }
      .receive(on: DispatchQueue.main)
      .subscribe(objectWillChange)
  }
/////////////////////////////////////

  static var shared = Settings()

  @Setting(key: "isBla") var isBla = true
}


@propertyWrapper
public struct Setting<T> {

  let key: String
  let defaultValue: T

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

  public var wrappedValue: T {
    get {
      let val = UserDefaults.standard.object(forKey: key) as? T
      return val ?? defaultValue
    }
    set {
      objectWillChange?.send()
      publisher?.subject.value = newValue
      UserDefaults.standard.set(newValue, forKey: key)
    }
  }

  public struct Publisher: Combine.Publisher {

    public typealias Output = T

    public typealias Failure = Never

    public func receive<Downstream: Subscriber>(subscriber: Downstream)
      where Downstream.Input == T, Downstream.Failure == Never {
        subject.subscribe(subscriber)
    }

    fileprivate let subject: Combine.CurrentValueSubject<T, Never>

    fileprivate init(_ output: Output) {
      subject = .init(output)
    }
  }

  private var publisher: Publisher?

  internal var objectWillChange: ObservableObjectPublisher?

  public var projectedValue: Publisher {
    mutating get {
      if let publisher = publisher {
        return publisher
      }
      let publisher = Publisher(wrappedValue)
      self.publisher = publisher
      return publisher
    }
  }
}
Nicolas Degen
  • 1,522
  • 2
  • 15
  • 24

0 Answers0