How to seamlessly wrap #Published variables with a UserDefaults propertyWrapper - swift

I've been looking for the perfect UserDefaults wrapper that
seamlessly encodes and decodes Data objects from storage
works with #Published
My starting point was this StackOverflow answer, but it uses object:forKey which doesn't work with custom objects (encoding URLs is always non-trivial for me).
My idea is to be able to use it like so:
struct Server: Identifiable, Codable, Equatable, Hashable { /* vars */ }
class ServerPickerViewModel: ObservableObject {
#Published(wrappedValue: Server.defaultServers.first!,
type: Server.self,
key: "currentServer")
var currentServer: Server?
}
To achieve this, I modified the code from #VictorKushnerov's answer:
import Combine
private var cancellables = [String: AnyCancellable]()
extension Published {
init<T: Encodable & Decodable>(wrappedValue defaultValue: T, type: T.Type, key: String) {
let decoder = JSONDecoder()
var value: T
if
let data = UserDefaults.standard.data(forKey: key),
let decodedVal = try? decoder.decode(T.self, from: data) {
value = decodedVal
} else {
value = defaultValue
}
self.init(initialValue: value) // <-- Error
cancellables[key] = projectedValue.sink { val in
let encoder = JSONEncoder()
let encodedVal = encoder.encode(val) // <-- Error
UserDefaults.standard.set(encodedVal, forKey: key)
}
}
}
There are currently two errors I can't get through, which are the following:
Cannot convert value of type 'T' to expected argument type 'Value' it still relying on the underlying #Published's Value generic type, I wish I could override that with my type T.
Instance method 'encode' requires that 'Published<Value>.Publisher.Output' (aka 'Value') conform to 'Encodable'

You can fix your compilation errors by using where Value : Codable to restrict your extension. Then, you can get rid of your T generic altogether (& you don't have to use the type argument either):
extension Published where Value : Codable {
init(wrappedValue defaultValue: Value, key: String) {
let decoder = JSONDecoder()
var value: Value
if
let data = UserDefaults.standard.data(forKey: key),
let decodedVal = try? decoder.decode(Value.self, from: data) {
value = decodedVal
} else {
value = defaultValue
}
self.init(initialValue: value)
cancellables[key] = projectedValue.sink { val in
let encoder = JSONEncoder()
do {
let encodedVal = try encoder.encode(val)
UserDefaults.standard.set(encodedVal, forKey: key)
} catch {
print(error)
assertionFailure(error.localizedDescription)
}
}
}
}
This being said, I'd probably take the path instead of creating a custom property wrapper instead that wraps #AppStorage instead of extending #Published

Related

Swift, help iterating over keyPaths and setting values

I've found that it's a little easier to explain what I'm doing by giving too much context for why I'm trying to do it, sorry.
I'm currently trying to add an encryption service to my project. It's nothing that'll get published I think though and I've mostly got it working. The problem that I'm having is that I have my model like this
struct Entity {
// All types are examples though I think they will be all optionals.
var prop1: String?
var prop2: Int?
var prop3: Bool?
var encryptedData: [Keypath:EncryptedData]
static var encryptableKeyPaths: [WritableKeyPath<Entity, Any?>]
}
As an example for what's happening, I can get the encryptionService to take in prop1, create an EncryptedData and put it in the encryptedData dictionary. I can even get the keyPath for the property. I can encrypt all the data and decrypt it just fine and get all the values properly, so I don't need help with that. But I'm struggling with 3 issues.
Getting the KeyPaths to be WritableKeyPaths so I can write to them with the values I need.
Setting the properties to nil once the values are encrypted so I'm not storing extra data.
Setting the properties to their values once their decrypted.
All three of these issues seem to revolve around making the KeyPaths into WritableKeyPaths.
This is the closest attempt I've gotten so far. You can copy the following code right into a playground and run it and it should work. Except it'll crash at the end. There are a couple of issues here, I'm losing the type safety as I have to make all the property types Initializable? which isn't great. Also, see that the values are permanently wrapped. I can't figure out how to prevent that. I had to mark Optional as conforming to Initializable to make this work. Lastly, the variable allStoredProperties doesn't let me write to them. I'm not sure how to properly convert it to WritableKeyPath from PartialKeyPath.
import UIKit
protocol Initializable {}
extension String: Initializable {}
extension Int: Initializable {}
extension Bool: Initializable {}
extension Optional: Initializable {}
protocol KeyPathIterable {
associatedtype Model
init()
static var allKeyPaths: [WritableKeyPath<Model, Initializable?>] { get }
}
extension KeyPathIterable {
var keyPathReadableFormat: [String: Initializable] {
var description: [String: Initializable] = [:]
let mirror = Mirror(reflecting: self)
for case let (label?, value) in mirror.children {
description[label] = (value as! Initializable)
}
return description
}
static var allStoredProperties: [PartialKeyPath<Self>] {
var members: [PartialKeyPath<Self>] = []
let instance = Self()
for (key, _) in instance.keyPathReadableFormat {
members.append(\Self.keyPathReadableFormat[key])
}
return members
}
static func setValue<Self: KeyPathIterable, T: Initializable>(on root: inout Self,
at keyPath: WritableKeyPath<Self, Initializable?>,
withValue value: T?) throws {
root[keyPath: keyPath] = value
}
}
struct Foo: KeyPathIterable {
typealias Model = Foo
var prop1: Initializable? // I want this to be String?
var prop2: Initializable? // I want this to be Int?
var prop3: Initializable? // I want this to be Bool?
init() {
self.prop1 = nil
self.prop2 = nil
self.prop3 = nil
}
static var allKeyPaths: [WritableKeyPath<Foo, Initializable?>] {
return [\Model.prop1, \Model.prop2, \Model.prop3]
}
}
var foo = Foo()
foo.prop1 = "Bar"
foo.prop2 = 1
foo.prop3 = true
print(foo.prop1 as Any)
let keyPath = \Foo.prop1
foo[keyPath: keyPath] = "Baz"
print(foo.prop1 as Any)
for path in Foo.allStoredProperties {
print("-=-=-")
print(path)
}
print("-=-=-=-=-=-=-=-")
do {
try Foo.setValue(on: &foo, at: keyPath, withValue: "BazBar" as Initializable?)
} catch {
print("Should never fail")
}
print(foo.prop1 as Any) // Returns Optional(Optional("BarBaz")) - I want this to be all the way unwrapped.
print("--------------")
let values1: [Initializable] = ["Hello World", 100, false]
do {
for (path, value) in zip(Foo.allKeyPaths, values1) {
try Foo.setValue(on: &foo,
at: path,
withValue: value as Initializable?)
}
} catch {
print("Success?")
}
print(foo.prop1 as Any)
print(foo.prop2 as Any)
print(foo.prop3 as Any)
print("----====----====----")
let values2: [Initializable] = ["Howdy", 0, false]
do {
for (path, value) in zip(Foo.allStoredProperties, values2) {
try Foo.setValue(on: &foo,
at: path as! WritableKeyPath<Foo, Initializable?>,
withValue: value as Initializable?)
}
} catch {
print("Always fails")
}
print("=-=-=-=-=-=-=-=")
print(foo)
I've looked all over google and youtube and everywhere and I can't seem to get this to work. I'm open to a different architecture if that work accomplish my goals better. Just a little frustrated. Thanks for your help.

Generics and Constraints (`Instance method '...' requires that 'T' conform to 'Decodable'`)

I have a generic struct allowing different types to be used. I do not want to constrain the whole struct to only Decodable items.
What is the best way to fix the following error, where I try to only execute some code if T conforms to Decodable:
Instance method '...' requires that 'T' conform to 'Decodable'
struct Something<T> {
...
func item<T>(from data: Data) -> T? where T: Decodable {
try? JSONDecoder().decode(T.self, from: data)
}
func getter() -> T {
let value = ...
if let value = value as? T { return value } // something simple like string
if let data = value as? Data, T.self is Decodable { // something complex
return item(from: data) ?? defaultValue // error is thrown here
}
return defaultValue
}
}
As you can see I'm checking the conformance with the if-clause, but that isn't enough to access the constrained method? :/
It makes no sense to me that T only needs to conform to Decodable in some part but not others. I would rewrite the struct as
struct Something<T: Decodable> {
func item(from data: Data) -> T? {
try? JSONDecoder().decode(T.self, from: data)
}
func getter() -> T {
let value = ...
if let data = value as? Data
return item(from: data) ?? defaultvalue
}
return defaultvalue
}
}
First of all you should constrain T to Decodable when you define the struct. Second, you must not define T as a generic parameter of the functions internally as it will not be treated as the same T the struct conforms to by the compiler. Instead it will be treated as a new and different generic type constraint (that you just happened to give the same name). Doing it like this is enough:
struct Something<T: Decodable> {
var defaultValue: T
var data: Data
func item(from data: Data) -> T? {
try? JSONDecoder().decode(T.self, from: data)
}
func getter() -> T {
item(from: data) ?? defaultValue
}
}
You can use an extension to define the more constrained method:
struct Something<T> {
var defaultValue: T
func getter() -> T {
return defaultValue
}
}
extension Something where T: Decodable {
func getter() -> T {
// I'm assuming here that you have a property data: Data
try? JSONDecoder().decode(T.self, from: data) ?? defaultValue
}
}
It's not entirely clear how you're going to use this type in a meaningful way. The way your code is constructed, value is Any type. Is this what you meant? (I'm guessing, not)
Somewhere, you'd need to make a concrete version of Something - i.e. it would be Something<Int> or Something<String> or Something<SomeDecodableType> - at that point T will be that concrete type, and as you can see, there's nothing common between the various versions of T, except Any.
So figure out what parts of Something truly are common.

Custom intialiser on Primitive types for JSONDecoder

How do I customise the behaviour of JSONDecoder for primitive types like Int, Bool?
Here is the problem:
Backend cannot be relied upon for types. Eg: Bool can come as true/false or "true"/"false"(bool can come wrapped in double quotes)
We have at least 300 Codable structs having average 15 properties in them and writing decoding logic is cumbersome. Also the logic remains more or less same hence the code has become repetitive
Hence I am looking for a solution such that if there is a Type mismatch primitive types should be able to handle it and if not then it should be set to nil provided that type is Optional.
I tried multiple approaches for this
1. Having Wrapper on all the primitive types and handling the decoding logic. Below is an example of wrapper on Bool
struct Bool1: Codable{
private var bool: Bool?
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let b = try? container.decode(Bool.self) {
self.bool = b
} else if let string = try? container.decode(String.self) {
if string.lowercased() == "true" {
self.bool = true
} else if string.lowercased() == "false" {
self.bool = false
} else {
throw Error()
}
}
}
}
but this created unnecessary confusion among fellow developers as Wrapped types do not come out as naturally as the Native ones. Also the value cannot be accessed directly (it always need xyz.bool) to extract the raw value
2. Create a new protocol inherited from Decodable and subclass JSONDecoder
protocol KKDecodable: Decodable {
init(decoder1: Decoder)
}
extension Bool: KKDecodable {
init(decoder1: Decoder) {
// Logic for creating Bool from different types
}
}
class JSONDecoder1: JSONDecoder {
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : KKDecodable {
// Some code that would invoke `init(decoder1: Decoder)`
// which is defined in `KKDecodable`
}
}
I was not able to write working code using this method
Property Wrapper
You can use property wrapper. Imagine this as an example:
#propertyWrapper
struct SomeKindOfBool: Decodable {
var wrappedValue: Bool?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringifiedValue = try? container.decode(String.self) {
switch stringifiedValue.lowercased() {
case "false": wrappedValue = false
case "true": wrappedValue = true
default: wrappedValue = nil
}
} else {
wrappedValue = try? container.decode(Bool.self)
}
}
}
Usage
struct MyType: Decodable {
#SomeKindOfBool var someKey: Bool?
}
You can use someKey value like a normal Bool now:
Test:
let jsonData = """
[
{ "someKey": "something else" },
{ "someKey": "true" },
{ "someKey": true }
]
""".data(using: .utf8)!
let decodedJSON = try! JSONDecoder().decode([MyType].self, from: jsonData)
for decodedType in decodedJSON {
print(decodedType.someKey)
}
Result:
nil
Optional(true)
Optional(true)
You can do similar for other situations and also any other type you need. Also note that I have changed the code to match your needs, but you can use the more compatible version that I posted as a gist here in GitHub

Property Wrapped Value if accessed from other files give non optional type

struct HarkAppPreference {
#UserPreferenceField(key: .userSelectedTags)
static var userSelectedTags: [MLQTag]
#UserPreferenceField(key: .userLastActivePlalist)
static var userLastActivePlaylist: [PlayableEntity]
#UserPreferenceField(key: .userLastActivePlayableEntityMeta)
static var userLastActivePlayableEntityMeta: LastPlayableEntityMeta
#UserPreferenceField(key: .currentUSer)
static var user: User
}
enum UserPreferenceFieldKey : String {
case userSelectedTags = "MLQUserSelectedTags"
case userLastActivePlalist = "MLQUserLastActivePlalist"
case userLastActivePlayableEntityMeta = "MLQUserLastActivePlayableEntityMeta"
case currentUSer
}
struct User : Codable {
let name : String
let phone: String
}
#propertyWrapper
struct UserPreferenceField<T : Codable> {
let key: UserPreferenceFieldKey
var wrappedValue : T? {
get {
guard let decodedData = UserDefaults.standard.value(forKey: key.rawValue) as? Data else { return nil }
return try? JSONDecoder().decode(T.self, from: decodedData)
}
set {
do {
let encodedValue = try JSONEncoder().encode(newValue)
UserDefaults.standard.set(encodedValue, forKey: key.rawValue)
} catch {
print("Error in saving codable value - \(error.localizedDescription)")
}
}
}
init(key:UserPreferenceFieldKey) {
self.key = key
}
}
This is my PropertyWrapper which I created to use for saving data to UserDefault, this worked to my expectations to use the value with optional chaining if used from within the file where I have written the PropertyWrapper code.
But this given an error Initializer for conditional binding must have Optional type, not 'User' when used in another file like below
class ABC {
func sss(){
if let _ = HarkAppPreference.user { // here is gives error
}
}
}
But if the same is used in the same file it works fine with optional chaining.
As per the reply from Apple Developers Team on Dev forum, It's not happening in Xcode 11.3 and worked fine when added optional to declared property.

Loop through Swift struct to get keys and values

I'd like to loop trough every key of mystruct and print its key and its value for every property.
struct mystruct {
var a = "11215"
var b = "21212"
var c = "39932"
}
func loopthrough {
for (key, value) in mystruct {
print("key: \(key), value: \(value)") // Type mystruct.Type does not conform to protocol 'Sequence'
}
}
But using the few lines from above I always get this error message:
Type mystruct.Type does not conform to protocol 'Sequence'
How can I avoid getting this message?
First of all let's use CamelCase for the struct name
struct MyStruct {
var a = "11215"
var b = "21212"
var c = "39932"
}
Next we need to create a value of type MyStruct
let elm = MyStruct()
Now we can build a Mirror value based on the elm value.
let mirror = Mirror(reflecting: elm)
The Mirror value does allow us to access all the properties of elm, here's how
for child in mirror.children {
print("key: \(child.label), value: \(child.value)")
}
Result:
key: Optional("a"), value: 11215
key: Optional("b"), value: 21212
key: Optional("c"), value: 39932
use following code to get array of all the properties
protocol PropertyLoopable
{
func allProperties() throws -> [String]
}
extension PropertyLoopable {
func allProperties() throws -> [String] {
var result: [String] = []
let mirror = Mirror(reflecting: self)
// Optional check to make sure we're iterating over a struct or class
guard let style = mirror.displayStyle, style == .struct || style == .class else {
throw NSError()
}
for (property,_) in mirror.children {
guard let property = property else {
continue
}
result.append(property)
// result[property] = value
}
return result
}
}
Now just
let allKeys = try self.allProperties()
Don't forgot to implement protocol
Hope it is helpful
You can use runtime introspection (on an instance of your type) combined with value-binding pattern matching to extract the property names and values; the latter used to unwrap the optional label property of the Mirror instance used to represent the sub-structure of your specific instance.
E.g.:
struct MyStruct {
let a = "11215"
let b = "21212"
let c = "39932"
}
// Runtime introspection on an _instance_ of MyStruct
let m = MyStruct()
for case let (label?, value) in Mirror(reflecting: m)
.children.map({ ($0.label, $0.value) }) {
print("label: \(label), value: \(value)")
} /* label: a, value: 11215
label: b, value: 21212
label: c, value: 39932 */
I hope it still helps someone:
This is my version of the protocol for more complicated classes/structs (Objects within Objects within Objects ;-) )
I am sure there is a more elegant functional solution but this was a quick and dirty solution, as I only needed it for a temporary log.
protocol PropertyLoopable {
func allProperties() -> [String: Any]
}
extension PropertyLoopable {
func allProperties() -> [String: Any] {
var result: [String: Any] = [:]
let mirror = Mirror(reflecting: self)
// make sure we're iterating over a struct or class
guard let style = mirror.displayStyle, style == .struct || style == .class else {
print("ERROR: NOT A CLASS OR STRUCT")
return result
}
for (property, value) in mirror.children {
guard let property = property else {
continue
}
// It was a very complicated struct from a JSON with a 4 level deep structure. This is dirty dancing, remove unnecessary "for" loops for simpler structs/classes
// if value from property is not directly a String, we need to keep iterating one level deeper
if value is String {
result.updateValue(value, forKey: property)
} else {
let mirror = Mirror(reflecting: value)
for (property, value) in mirror.children {
guard let property = property else {
continue
}
//let's go for a second level
if value is String {
result.updateValue(value, forKey: property)
} else {
let mirror = Mirror(reflecting: value)
for (property, value) in mirror.children {
guard let property = property else {
continue
}
//3rd level
if value is String {
result.updateValue(value, forKey: property)
} else {
let mirror = Mirror(reflecting: value)
for (property, value) in mirror.children {
guard let property = property else {
continue
}
result.updateValue(value, forKey: property)
}
}
}
}
}
}
}
return result
}
}