How do i get labels from mirror with empty values - swift

What i want to do is when i start the app, save all the names (in userDefault) of the constants from all models. I plan on doing it with a function looking something like:
public static func setup(models: [Codable]) {
models.forEach { (model) in
let mirr = Mirror.init(reflecting: model)
mirr.children.map({
UserDefaults.save("\(type(of: model))+\($0.label!)")})
}
}
Please note that i'm not finished yet.
What i know of there are 3 solutions to using this method. As the mirror wont show me labels unless the variable / constant has an actual value. It becomes really ugly and i wonder if i can do some work around because current you call it like this.
Scenario A:
public struct Testing: Codable {
let name: String = ""
let sex: String = ""
}
setup(models: [Testing()])
Scenario B:
public struct testing: Codable {
let name: String
let sex: String
}
setup(models: [Testing(name: "", sex: "")])
Scenario C:
public struct testing: Codable {
let name: String
let sex: String
init(name: String = "", sex: String = "") {
self.name = name
self.sex = sex
}
}
setup(models: [Testing()])
So basically what i want to do is:
public struct testing: Codable {
let name: String
let sex: String
}
setup(models: [Testing()])
// or
setup(models: [Testing.self])
Or kinda anything that wont force me to init the values.
I guess it can't be done, but maybe someone have some hack out there that work...
Thanks in advance.

You can simply update your setup(models:) method to,
public func setup(models: [Codable]) {
let arr: [String] = models.compactMap {
let mirror = Mirror(reflecting: $0)
if let name = mirror.children.first(where: { $0.label == "name" }) {
let value = "\(type(of: $0))+\(name.value)"
return value
}
return nil
}
UserDefaults.standard.set(arr, forKey: "Names")
}

Related

Filter array of nested structs

I'm looking for a nice swift solution for the following problem:
Lets say we have 2 structs like so:
struct Person {
let name: String
let age: Int
let skills: Skills
init(name: String, age: Int, skills: Skills) {
self.name = name
self.age = age
self.skills = skills
}
}
struct Skills {
let canUseBow: Bool
let canUseSword: Bool
let canUseShield: Bool
init(canUseBow: Bool, canUseSword: Bool, canUseShield: Bool) {
self.canUseBow = canUseBow
self.canUseSword = canUseSword
self.canUseShield = canUseShield
}
}
Now lets say I have an array of Person where each person has their own skills obviously where the corrosponding values can be true or false.
Lets say I want another array of just people that have the skill canUseBow as true so that skill must be set to true , how would I go about filtering out the Persons that do not have canUseBow set to true?
I was thinking in a direction of:
filteredPersons = persons.filter {
$0.skills
}
But that way it would require me to than select something after skills for example
$0.skills.canUseBow
That does not seem very future proof, lets say I would want to add more skills than I would also have to change the filter method again. Are there better ways to go about this?
You can try this with an OptionSet that can hold all of these flags for you in a simple Int storage.
import Foundation
struct Person {
let name: String
let age: Int
let skills: Skills
init(name: String, age: Int, skills: Skills) {
self.name = name
self.age = age
self.skills = skills
}
}
struct Skills: OptionSet {
let rawValue: Int
init(rawValue: Int) {
self.rawValue = rawValue
}
static let canUseBow = Skills(rawValue: 1 << 0)
static let canUseSword = Skills(rawValue: 1 << 1)
static let canUseShield = Skills(rawValue: 1 << 2)
init(json: [String: Bool]) {
var skills = Skills(rawValue: 0)
if let canUseBow = json["can_use_bow"], canUseBow {
skills.insert(.canUseBow)
}
if let canUseSword = json["can_use_sword"], canUseSword {
skills.insert(.canUseSword)
}
if let canUseShield = json["can_use_shield"], canUseShield {
skills.insert(.canUseShield)
}
self = skills
}
}
How to instantiate Skills?
let skills = Skills(json: [
"can_use_bow" : true,
"can_use_sword" : true,
"can_use_shield" : false,
])
How to filter based on multiple skills?
let targetSkills: Skills = [.canUseBow, .canUseSword]
let persons: [Person] = []
let filteredPersons = persons.filter {
targetSkills.isSubset(of: $0.skills)
}

Exclude CodingKeys that doesn't need to be altered?

Say I have a struct User model which has many properties in it.
struct User: Codable {
let firstName: String
let lastName: String
// many more properties...
}
As you can see above it conforms to Codable. Imagine if the lastName property is should be encoded/decoded as secondName and I would like to keep it as lastName at my end, I need to add the CodingKeys to the User model.
struct User: Codable {
//...
private enum CodingKeys: String, CodingKey {
case firstName
case lastName = "secondName"
// all the other cases...
}
}
Is there any possible way to avoid including all the cases in CodingKeys that have the same value as rawValue like the firstName in the above example (Feels redundant)? I know if I avoid the cases in CodingKeys it won't be included while decoding/encoding. But, is there a way I could override this behaviour?
There is a codable way, but the benefit is questionable.
Create a generic CodingKey
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }
init?(intValue: Int) { self.stringValue = String(intValue); self.intValue = intValue }
}
and add a custom keyDecodingStrategy
struct User: Codable {
let firstName: String
let lastName: String
let age : Int
}
let jsonString = """
{"firstName":"John", "secondName":"Doe", "age": 30}
"""
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ keyPath -> CodingKey in
let key = keyPath.last!
return key.stringValue == "secondName" ? AnyKey(stringValue:"lastName")! : key
})
let result = try decoder.decode(User.self, from: data)
print(result)
} catch {
print(error)
}
There is not such a feature at this time. But you can take advantage of using computed properties and make the original one private.
struct User: Codable {
var firstName: String
private var secondName: String
var lastName: String {
get { secondName }
set { secondName = newValue }
}
}
So no need to manual implementing of CodingKeys at all and it acts exactly like the way you like. Take a look at their counterparts:

What is the simplest way to instantiate a new Codable object in Swift?

I have a Codable class:
class Task: Codable {
var name: String
}
When I try to instantiate it:
let newTask = Task()
allTasks.append(newTask)
It gives me error:
Missing argument for parameter 'from' in call
All I want is to insert a new object (newTask) into an array. What is the simplest way to do this?
You can inherit the initializer from NSObject:
class Task: NSObject, Codable {
var name: String = ""
}
let newTask = Task()
If you don't want to inherit NSObject, then just create your own initializer:
class Task: Codable {
var name: String?
init() {
}
}
If you don't want to make name optional (or set it to a default), it has to be initialized in init() such as:
class Task: Codable {
var name: String
init(withName name: String) {
self.name = name
}
}
let newTask = Task(withName: "ikevin8me")
Yet another solution is to use struct
struct Task: Codable {
var name: String
}
let task = Task(name: "myname")
Your Task class doesn't provide its own initializer so it gets the one defined in the Codable protocol (which it gets from the Decodable protocol).
Either add your own explicit init that takes a name parameter or change your class to a struct. Either way, you need to create a Task by passing in a name value so the name property can be initialized.
None of this addresses the fact that the code you posted makes no use of Codable so maybe there is no need for your class (or struct) to conform to Codable.
The Task class doesn't provide any initializer to initialize an object that's why it's taking initializer from Codable protocol, Provide your own initializer to initialize an object.
Usage:
1.With Class
class Task: Codable {
var name: String
init(_ name : String) {
self.name = name
}
}
var task = Task("Jarvis")
2.With struct:
struct Task: Codable {
var name: String
}
let task = Task(name: "jarvis")
I would not assign a default value to the property but implement the expected init method and a convenience variant that didn't take any arguments or alternatively have a factory method that creates an "empty" Task
Here is the code with both options
class Task: Codable {
var name: String
init(_ name: String) {
self.name = name
}
convenience init() {
self.init("")
}
static func emptyTask() -> Task {
return Task("")
}
}
You could instantiate a object from json (for example when you use an API) like this:
struct Person: Codable {
var firstName: String
var lastName: String
var age: Int
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age = "age"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstName = try container.decode(String.self, forKey: .firstName)
lastName = try container.decode(String.self, forKey: .lastName)
age = try container.decode(Int.self, forKey: .age)
}
init(data: Data) {
let decoder = JSONDecoder()
do {
let userDecoded = try decoder.decode(Person.self, from: data)
self = userDecoded
}
catch {
age = 0
firstName = ""
lastName = ""
}
}
func fullName() -> String {
return "\(self.firstName) \(self.lastName)"
}
}

I found a generic way to turn a class into a dictionary, is there a generic way to reverse that process?

So I'm trying to build a data-mapper style ORM in Swift. I was trying to find a way to turn any entity into a dictionary so I could map that to a database table. I managed to do that with this code:
public protocol Entity: class {
var id: Int? { get set }
}
func unwrap(any:Any) -> Any? {
let mi = Mirror(reflecting: any)
if mi.displayStyle != .Optional {
return any
}
if mi.children.count == 0 {
return nil
}
let (_, some) = mi.children.first!
return some
}
public func serialize<T: Entity>(entity: T) -> [String:String] {
let aMirror = Mirror(reflecting: entity)
var dict = [String:String]()
for child in aMirror.children {
if let label = child.label where !label.hasPrefix("_") {
switch unwrap(child.value) {
case let entity as Entity:
if let id = entity.id {
dict[label] = String(id)
}
case let .Some(test):
dict[label] = String(test)
default: break
}
}
}
return dict
}
public class User: Entity {
public var id: Int?
var username: String
var password: String
var role: UserRole?
public init(username: String, password: String) {
self.username = username
self.password = password
}
public func assignRole(role: UserRole) {
self.role = role
}
}
public class UserRole: Entity {
public var id: Int?
var name: String
var description: String
public init(name: String, description: String) {
self.name = name
self.description = description
}
}
So now I can do:
let newUser = User(username: "NotMyRealName", password: "SomeKindOfPassword")
let adminRole = UserRole(name: "admin", description: "Administrator of the website")
adminRole.id = 1
newUser.assignRole(adminRole)
print(serialize(newUser))
Which will print: ["password": "SomeKindOfPassword", "role": "1", "username": "NotMyRealName"]
That's all well and good, but now I want to reverse this process.
I'd like to write a function that could use like so:
let user = unserialize(["username":"Me", "password":"secret"]) as? User
Is that possible you think? I understand it might be a bit of a hack if I'd try to bypass the init method, but I'd like to try.

Access properties via subscripting in Swift

I have a custom class in Swift and I'd like to use subscripting to access its properties, is this possible?
What I want is something like this:
class User {
var name: String
var title: String
subscript(key: String) -> String {
// Something here
return // Return the property that matches the key…
}
init(name: String, title: String) {
self.name = name
self.title = title
}
}
myUser = User(name: "Bob", title: "Superboss")
myUser["name"] // "Bob"
Update: The reason why I'm looking for this is that I'm using GRMustache to render from HTML templates. I'd like to be able to just pass my model object to the GRMustache renderer…
GRMustache fetches values with the keyed subscripting objectForKeyedSubscript: method and the Key-Value Coding valueForKey: method. Any compliant object can provide values to templates.
https://github.com/groue/GRMustache/blob/master/Guides/view_model.md#viewmodel-objects
This is a bit of a hack using reflection. Something along the lines of the following could be used.
protocol PropertyReflectable { }
extension PropertyReflectable {
subscript(key: String) -> Any? {
let m = Mirror(reflecting: self)
for child in m.children {
if child.label == key { return child.value }
}
return nil
}
}
struct Person {
let name: String
let age: Int
}
extension Person : PropertyReflectable {}
Then create a Person and access it's keyed properties.
let p = Person(name: "John Doe", age: 18)
p["name"] // gives "John Doe"
p["age"] // gives 18
You could modify the subscript to always return an interpolated string of the property value.
Adding some syntax sugar to Benzi's answer:
protocol PropertyReflectable { }
extension PropertyReflectable {
subscript(key: String) -> Any? {
let m = Mirror(reflecting: self)
return m.children.first { $0.label == key }?.value
}
}
struct Person: PropertyReflectable {
let name: String
let age: Int
}
Then create a Person and access it's keyed properties.
let p = Person(name: "John Doe", age: 18)
p["name"] // gives "John Doe"
p["age"] // gives 18
Using valueForKey should enable you to access properties using their names. Be sure that you're working with a object that inherit NSObject
class people: NSObject {
var age: NSString = "44"
var height: NSString = "153"
}
let person:people = people()
let stringVariable = "age"
person.valueForKey("age")
// Print "44"
person.valueForKey("\(stringVariable)")
// Print "44"
(GRMustache author here)
Until a swift-oriented Mustache library is out, I suggest having your classes inherit from NSObject (so that they have the valueForKey: method). GRMustache will then fetch values with this method.
In case this would still not work (blank values in the rendering), you may try to disable GRMustache security features (see https://github.com/groue/GRMustache/blob/master/Guides/security.md#disabling-safe-key-access)
Should you experience any other trouble, please open an issue right into the repository: https://github.com/groue/GRMustache/issues
EDIT February 2, 2015: GRMustache.swift is out: http://github.com/groue/GRMustache.swift
Shim's answer above doesn't work anymore in Swift 4. There are two things you should be aware of.
First of all, if you want to use value(forKey:) function, your class must inherit NSObject.
Secondly, since Objective-C doesn't know anything about value type, you have to put the #objc keyword in front of your value type properties and Swift will do the heavy-lifting for you.
Here is the example:
import Foundation
class Person: NSObject {
#objc var name: String = "John Dow"
#objc var age: Int = 25
#objc var height: Int = 180
subscript(key: String) -> Any? {
return self.value(forKey: key)
}
}
let person: Person = Person()
person["name"] // "John Dow"
person["age"] // 25
person["height"] // 180
I suppose you could do:
class User {
let properties = Dictionary<String,String>()
subscript(key: String) -> String? {
return properties[key]
}
init(name: String, title: String) {
properties["name"] = name
properties["title"] = title
}
}
Without knowing your use case I would strongly advise against doing this.
Another approach:
class User {
var name : String
var title : String
subscript(key: String) -> String? {
switch key {
case "name" : return name
case "title" : return title
default : return nil
}
}
init(name: String, title: String) {
self.name = name
self.title = title
}
}
It might be worth noting that Swift doesn't appear to currently support reflection by names. The reflect function returns a Mirror whose subscript is Int based, not String based.