Skip to content

Instantly share code, notes, and snippets.

@florianpircher
Last active January 15, 2022 21:00
Show Gist options
  • Save florianpircher/626174760c2d91bab6defbf1b2b3d4cf to your computer and use it in GitHub Desktop.
Save florianpircher/626174760c2d91bab6defbf1b2b3d4cf to your computer and use it in GitHub Desktop.
Property wrappers for user defaults. For user defaults publishers, see: https://gist.github.com/florianpircher/4513f8def656fc4fe427345bce7b052b
//
// User Defaults Property Wrappers.swift
//
// Copyright 2021 Florian Pircher
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
/// The values of user defaults are located under the `values` property of `NSUserDefaultsController`.
/// This prefix is used to access user defaults keys on `NSUserDefaultsController`.
fileprivate let userDefaultsKeyPathPrefix = "values."
extension UserDefaults {
@propertyWrapper class Bool<Value>: NSObject, Publisher {
typealias RawValue = Swift.Bool
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: Bool<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.bool(forKey: key)
}
convenience init(key: Swift.String) where Value == Swift.Bool {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
@propertyWrapper class Int<Value>: NSObject, Publisher {
typealias RawValue = Swift.Int
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: Int<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.integer(forKey: key)
}
convenience init(key: Swift.String) where Value == Swift.Int {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
@propertyWrapper class Float<Value>: NSObject, Publisher {
typealias RawValue = Swift.Float
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: Float<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.float(forKey: key)
}
convenience init(key: Swift.String) where Value == Swift.Float {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
@propertyWrapper class Double<Value>: NSObject, Publisher {
typealias RawValue = Swift.Double
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: Double<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.double(forKey: key)
}
convenience init(key: Swift.String) where Value == Swift.Double {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
@propertyWrapper class String<Value>: NSObject, Publisher {
typealias RawValue = Swift.String?
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: String<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.string(forKey: key)
}
convenience init(key: Swift.String) where Value == Swift.String? {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
@propertyWrapper class URL<Value>: NSObject, Publisher {
typealias RawValue = Foundation.URL?
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: URL<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.url(forKey: key)
}
convenience init(key: Swift.String) where Value == Foundation.URL? {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
@propertyWrapper class Data<Value>: NSObject, Publisher {
typealias RawValue = Foundation.Data?
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: Data<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.data(forKey: key)
}
convenience init(key: Swift.String) where Value == Foundation.Data? {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
@propertyWrapper class Array<Value>: NSObject, Publisher {
typealias RawValue = [Any]?
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: Array<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.array(forKey: key)
}
convenience init(key: Swift.String) where Value == [Any]? {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
@propertyWrapper class StringArray<Value>: NSObject, Publisher {
typealias RawValue = [Swift.String]?
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: StringArray<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.stringArray(forKey: key)
}
convenience init(key: Swift.String) where Value == [Swift.String]? {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
@propertyWrapper class Object<Value>: NSObject, Publisher {
typealias RawValue = Any?
typealias Output = Value
typealias Failure = Never
let key: Swift.String
private let transform: (RawValue) -> Value
private let currentValueSubject: CurrentValueSubject<Output, Failure>
var wrappedValue: Value {
currentValueSubject.value
}
var projectedValue: Object<Value> { self }
static func accessRawValue(forKey key: Swift.String) -> RawValue {
UserDefaults.standard.object(forKey: key)
}
convenience init(key: Swift.String) where Value == Any? {
self.init(key: key) { $0 }
}
init(key: Swift.String, transform: @escaping (RawValue) -> Value) {
self.key = key
self.transform = transform
self.currentValueSubject = CurrentValueSubject(transform(Self.accessRawValue(forKey: key)))
super.init()
NSUserDefaultsController.shared.addObserver(self, forKeyPath: userDefaultsKeyPathPrefix + key, options: [], context: nil)
}
func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
currentValueSubject.receive(subscriber: subscriber)
}
override func observeValue(forKeyPath keyPath: Swift.String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
guard let keyPath = keyPath,
keyPath.hasPrefix(userDefaultsKeyPathPrefix),
keyPath.dropFirst(userDefaultsKeyPathPrefix.count) == key,
context == nil else {
// discard any values that are unrelated to the registered user defaults key
return
}
let newValue = transform(Self.accessRawValue(forKey: key))
currentValueSubject.send(newValue)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment