Skip to content

Commit

Permalink
Merge pull request #2 from amine2233/feature/usererdefaults-combine
Browse files Browse the repository at this point in the history
feat: add propertywrapper to userdefaults
  • Loading branch information
amine2233 committed Dec 27, 2022
2 parents 82df1df + 5c1d69f commit c62f124
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public extension Publishers {
public typealias Failure = Never

private let userDefaults: UserDefaults
private let key: Key
private let key: UserDefaultsKey

public init(userDefaults: UserDefaults, key: Key) {
public init(userDefaults: UserDefaults, key: UserDefaultsKey) {
self.userDefaults = userDefaults
self.key = key
}
Expand All @@ -36,11 +36,11 @@ public extension Publishers {
extension Publishers.DefaultsObservation {
private final class SubscriptionDefaultsObservation<S: Subscriber, T: PropertyListValue>: NSObject, Subscription where S.Input == T? {
private var subscriber: S?
private var key: Key
private var key: UserDefaultsKey
private var isDisposed: Bool = false
private var userDefaults: UserDefaults

init(subscriber: S, userDefaults: UserDefaults, key: Key) {
init(subscriber: S, userDefaults: UserDefaults, key: UserDefaultsKey) {
self.subscriber = subscriber
self.userDefaults = userDefaults
self.key = key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,17 @@ import Combine
import CombineExtension

final class DefaultsObservation<T: PropertyListValue>: NSObject, Cancellable {
let key: Key
private var onChange: (T?) -> Void
let key: UserDefaultsKey
var isDisposed: Bool = false
var userDefaults: UserDefaults
private var onChange: (T?) -> Void

init(key: Key, userDefaults: UserDefaults = .standard, onChange: @escaping (T?) -> Void) {
init(key: UserDefaultsKey, userDefaults: UserDefaults = .standard, onChange: @escaping (T?) -> Void) {
self.key = key
self.onChange = onChange
self.userDefaults = userDefaults
}

func configure() -> DefaultsObservation<T> {
userDefaults.addObserver(self, forKeyPath: key.rawValue, options: [.new], context: nil)
return self
super.init()
self.userDefaults.addObserver(self, forKeyPath: key.rawValue, options: [.new], context: nil)
}

// swiftlint:disable block_based_kvo
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//
// File.swift
//
//
// Created by amine on 27/12/2022.
//

import Combine
#if canImport(SwiftUI)
import SwiftUI

/// A property wrapper type that reflects a value from `UserDefaults` and
/// invalidates a view on a change in value in that user default.
@frozen @propertyWrapper
public struct DefaultsStorage<Value: PropertyListValue>: DynamicProperty {

@ObservedObject private var _value: DefaultsObservationStorage<Value>

private init(
value: Value,
userDefaults: UserDefaults = .standard,
key: UserDefaultsKey
) {
_value = DefaultsObservationStorage(
defaultValue: value,
key: key,
userDefaults: userDefaults
)
}

public var wrappedValue: Value {
get {
_value.value
}
nonmutating set {
_value.value = newValue
}
}

public var projectedValue: Binding<Value> {
Binding(
get: { self.wrappedValue },
set: { self.wrappedValue = $0 }
)
}

public var publisher: AnyPublisher<Value, Never> {
_value.publisher
}
}

@usableFromInline
final class DefaultsObservationStorage<T: PropertyListValue>: NSObject, ObservableObject {
var defaultValue: T
let key: UserDefaultsKey
var isDisposed: Bool = false
var userDefaults: UserDefaults
var value: T {
get { userDefaults.value(forKey: key.rawValue) as? T ?? defaultValue }
set { userDefaults.setValue(newValue, forKey: key.rawValue) }
}
var publisher: AnyPublisher<T, Never> { subject.eraseToAnyPublisher() }

private var subject: CurrentValueSubject<T, Never>

init(
defaultValue: T,
key: UserDefaultsKey,
userDefaults: UserDefaults = .standard
) {
self.defaultValue = defaultValue
self.key = key
self.userDefaults = userDefaults
self.subject = CurrentValueSubject(defaultValue)
super.init()
self.userDefaults.addObserver(self, forKeyPath: key.rawValue, options: [.new], context: nil)
}

// swiftlint:disable block_based_kvo
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context _: UnsafeMutableRawPointer?) {
guard let change = change, object != nil, keyPath == key.rawValue else { return }
let newValue = change[.newKey] as? T
subject.send(newValue ?? defaultValue)
}
// swiftlint:enable block_based_kvo
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import CombineExtension

public protocol PropertyListValue {}

public struct Key: RawRepresentable {
public struct UserDefaultsKey: RawRepresentable {
public let rawValue: String

public init(rawValue: String) {
self.rawValue = rawValue
}
}

extension Key: ExpressibleByStringLiteral {
extension UserDefaultsKey: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
rawValue = value
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ import Combine
import CombineExtension

extension CombineExtension where Base: UserDefaults {
public func change<T: PropertyListValue>(key: Key) -> AnyPublisher<T?, Never> {
public func change<T: PropertyListValue>(key: UserDefaultsKey) -> AnyPublisher<T?, Never> {
Publishers.DefaultsObservation(userDefaults: base, key: key)
.eraseToAnyPublisher()
}

public func onChange<T:PropertyListValue>(key: Key, callback: @escaping (T?) -> Void) -> AnyCancellable {
public func onChange<T:PropertyListValue>(key: UserDefaultsKey, callback: @escaping (T?) -> Void) -> AnyCancellable {
DefaultsObservation(key: key, userDefaults: base, onChange: callback)
.configure()
.eraseToAnyCancellable()
}
}

0 comments on commit c62f124

Please sign in to comment.