Happy holidays!
I have a property wrapper that is used to define the key in a key-value pair.
#propertyWrapper
final public class KeyValuePair<T> {
public var wrappedValue: T?
private(set) var key: String
public init(key: String) {
self.key = key
}
}
EDIT:
And using the following protocol, to which all KV models should conform...
protocol Parametrized {
var toParameters: [String: Any] { get }
}
the property wrapper is used in the following fashion:
struct Person: Parametrized {
#KeyValuePair(key: "first_name") var firstName: String?
#KeyValuePair(key: "last_name") var lasteName: String?
#KeyValuePair(key: "age") var age: Int?
#KeyValuePair(key: "isSingle") var isSingle: Bool?
#KeyValuePair(key: "isOpenForDating") var isOpenForDating: Bool?
}
var missUniverse2022 = Person()
missUniverse2022.firstName = "Miss"
missUniverse2022.lastName = "Universe"
missUniverse2022.age = 23
missUniverse2022.isSingle = true // Rad <3
missUniverse2022.isOpenForDating = false // Sad :D
So, the whole idea is to generate a dictionary of type [String: Any] that holds the key-value pairs from that struct using Mirror.
The problem is that the following code is not working (I know why, but I can't figure out the workaround):
extension Parametrized {
var toParameters: [String: Any] {
var parameters = [String: Any]()
for child in Mirror(reflecting: self).children {
/***********************************************
Generic parameter 'T' could not be inferred in cast to 'KeyValuePair'
Explicitly specify the generic arguments to fix this issue
***********************************************/
guard let kvPair = child.value as? KeyValuePair else { continue }
parameters[kvPair.key] = kvPair.wrappedValue
}
return parameters
}
}
So, I turn to the experts here: how to use the mirror logic to get the k-v pairs and put them in a collection?
I really appreciate any help you can provide.
Thanks to the Swift community, I got my answer.
https://forums.swift.org/t/property-wrappers-with-generics-vs-mirroring/54325/4
Related
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.
I wanna collect all cities with towns in a dictionary like [String: [String]]. But because of models, I cannot append townNames to dictionary. My models like below. Because of the [TownModel], I cannot get townName as string. It works with [String: [TownModel]] dictionary. But I want to use my [String: [String]] dictionary. What should I add to my code ?
open class CityListResponseModel: BaseResponseProtocol {
public var cityCode: Int?
public var cityName: String?
public var towns: [TownModel]?
}
open class TownModel: Codable {
public var townCode: Int?
public var townName: String?
}
//My code
var dictionary: [String: [String]]
let dict = list.reduce(into: [:]) { $0[$1.cityName, default: []].append($1.towns) }
First, improve your model. There is no reason to have an optional array, or a non-final class.
(There may also be no reason for cityCode or cityName to be optional either. The "city" prefix also seems redundant, as does the "town" one. Check on all that.)
final class CityListResponseModel: BaseResponseProtocol {
public var cityCode: Int?
public var cityName: String?
public var towns: [TownModel]
}
With that,
Dictionary(
list.compactMap { city in
city.cityName.map { ($0, city.towns.compactMap(\.townName)) }
},
uniquingKeysWith: +
)
You can use like:
var dictionary: [String: [String]] = [:]
let list: [CityListResponseModel] = []
list.forEach { (city) in
dictionary[city.cityName ?? ""] = city.towns?.map({$0.townName ?? ""})
}
I need to create dictionary that holds key-value pairs where Key of type String and Value of type Array of Struct, and need to subscript to get the count of Value of given Key. Please help me. Here is how i am creating the desired type, is it correct? and i don't know how to access specific value of a given key.
var BypassDictionary = [String: [ByPassList]]()
struct ByPassData
{
var name: String
var address: String
}
var bypassDictionary = [String: [ByPassData]]()
bypassDictionary["test"] = [ByPassData.init(name: "John", address: "FL"),ByPassData.init(name: "Joe", address: "CL")]
bypassDictionary["test1"] = [ByPassData.init(name: "John", address: "FL")]
if let getDataForTest = bypassDictionary["test"] {
let countForTest = getDataForTest.count
print(countForTest)
}
Here is a simple example
struct ByPassList {
var someValue: Int
}
var bypassDictionary = [String: [ByPassList]]()
bypassDictionary["hello"] = [ByPassList(someValue: 1), ByPassList(someValue: 2)]
if let test = bypassDictionary["hello"] {
print(test[1])
}
Note that var name should begin with a lowercase
Here is the Loop-based
struct ByPassList {
var someValue: Int
}
var dic = [String: [ByPassList]]()
dic["values"] = Array(0...100).map({ByPassList(someValue: $0)})
print(dic["values"])
I have a Single Class like this:
class Single {
static let sharedInstance: Single = Single()
...
}
But I want use Generic in this Class like this:
class Single<T: Hashable> {
static let sharedInstance: Single = Single()
var dic: [T: Any] = [:] // Here use the Generic
}
I got this result from Xcode
Static stored properties not supported in generic types
I have search this error in stackoverflow, but all the answer for this is not suit for me. Like this one(How to define static constant in a generic class in swift?)
How can I solve this?
You can declare a generic type using a static computed property as follows:
class Single<T: Hashable> {
static var sharedInstance: Single? {
if self.sharedInstance != nil {
return self.sharedInstance
} else {
return Single()
}
}
var dic: [T: Any] = [:]
}
I take it you simply want the one singleton to be able to store any hashable key in your dictionary? If so, do the following:
class Single {
static let sharedInstance: Single = Single()
var dic: [AnyHashable : Any] = [:]
}
Single.sharedInstance.dic["Grimxn"] = 1
Single.sharedInstance.dic[1] = "Grimxn"
Single.sharedInstance.dic // [1: "Grimxn", "Grimxn": 1] as required
This is simplified code that doesn't compile. Is there anyway to make this work in Swift? Thanks.
protocol Person {
var name:String { get }
var age:Int { get }
}
extension Dictionary : Person {
var name: String {
return self["name"] as String
}
var age: Int {
return self["Int"] as Int
}
}
Let me give some context to why I would want to do this.
Lets say I have some person data coming in over the wire as json. As I pass it through JSONSerialization I get a [String:AnyObject] Dictionary back.
So I would like to declare the JSON data interfaces in protocols, make the dictionary objects conform to the protocols and then extract the values from the dictionaries via typed properties, rather then via magic strings and casts. This way the client code would only know about protocol types even though they are implemented as dictionaries behind the curtain.
Not sure it's doable or a good idea, just wanted to try it. But compiler is giving me all sorts of trouble.
I understand you want to encapsulate the logic to link a json to it's model representation.
The suggested solution
First of all I am suggesting another way to achieve what you are looking for instead
Look at this Struct.
struct Person {
let name: String
let age: Int
init?(json: [String:Any]) {
guard let name = json["name"] as? String, age = json["age"] as? Int else { return nil }
self.name = name
self.age = age
}
}
The logic to extract data from the json is encapsulated into its initializer. And if the provided json is not valid the initialization fails. It's safe because it will never crash and it's easy to use.
The direct answer (don't do this at home!)
protocol Person {
var name: String? { get }
var age: Int? { get }
}
extension Dictionary : Person {
private var dictWithStringKeys: [String:Any] {
return reduce([String:Any]()) { (dict, elm) -> [String:Any] in
var dict = dict
if let key = elm.0 as? String {
dict[key] = elm.1
}
return dict
}
}
var name: String? {
return dictWithStringKeys["name"] as? String
}
var age: Int? {
return dictWithStringKeys["age"] as? Int
}
}