how to get single variable name from struct - swift

I have a core data framework to handle everything you can do with coredata to make it more cooperateable with codable protocol. Only thing i have left is to update the data. I store and fetch data by mirroring the models i send as a param in their functions. Hence i need the variable names in the models if i wish to only update 1 specific value in the model that i request.
public func updateObject(entityKey: Entities, primKey: String, newInformation: [String: Any]) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityKey.rawValue)
do {
request.predicate = NSPredicate.init(format: "\(entityKey.getPrimaryKey())==%#", primKey)
let fetchedResult = try delegate.context.fetch(request)
print(fetchedResult)
guard let results = fetchedResult as? [NSManagedObject],
results.count > 0 else {
return
}
let key = newInformation.keys.first!
results[0].setValue(newInformation[key],
forKey: key)
try delegate.context.save()
} catch let error {
print(error.localizedDescription)
}
}
As you can see the newInformation param contains the key and new value for the value that should be updated. However, i dont want to pass ("first": "newValue") i want to pass spots.first : "newValue"
So if i have a struct like this:
struct spots {
let first: String
let second: Int
}
How do i only get 1 name from this?
i've tried:
extension Int {
var name: String {
return String.init(describing: self)
let mirror = Mirror.init(reflecting: self)
return mirror.children.first!.label!
}
}
I wan to be able to say something similar to:
spots.first.name
But can't figure out how

Not sure that I understood question, but...what about this?
class Spots: NSObject {
#objc dynamic var first: String = ""
#objc dynamic var second: Int = 0
}
let object = Spots()
let dictionary: [String: Any] = [
#keyPath(Spots.first): "qwerty",
#keyPath(Spots.second): 123,
]
dictionary.forEach { key, value in
object.setValue(value, forKeyPath: key)
}
print(object.first)
print(object.second)
or you can try swift keypath:
struct Spots {
var first: String = ""
var second: Int = 0
}
var spots = Spots()
let second = \Spots.second
let first = \Spots.first
spots[keyPath: first] = "qwerty"
spots[keyPath: second] = 123
print(spots)
however there will be complex (or impossible) problem to solve if you will use dictionary:
let dictionary: [AnyKeyPath: Any] = [
first: "qwerty",
second: 123
]
you will need to cast AnyKeyPath back to WritableKeyPath<Root, Value> and this seems pretty complex (if possible at all).
for path in dictionary.keys {
print(type(of: path).rootType)
print(type(of: path).valueType)
if let writableKeyPath = path as? WritableKeyPath<Root, Value>, let value = value as? Value { //no idea how to cast this for all cases
spots[keyPath: writableKeyPath] = value
}
}

Related

How do I remove "Optional()" from object in an array

So im using CloudKit and fetching all the data into an array as [StartDay], my StartDay class looks like this:
import UIKit
import CloudKit
class StartDay {
var recordID: CKRecord.ID!
var wakeUp: String!
var sleptWell: String!
var dNN: String!
var created: String! {
get {
return created
}
}
}`
My function loads get an arraylist, which contains information received from the database. In my database it stands like this: "22.01.09:
func checkIfButtonShouldBeEnabled(startDayList: [StartDay]){
let startDayDates = startDayList.map{$0.created}
for i in 0..<startDayDates.count {
print(startDayDates)
}
}`
OUTPUT:
Optional("22.01.2019")
Optional("22.01.2019")
I want to remove "Optional()", so it only says "22.01.2019", how can I do so?
UPDATE: FETCH FUNC
func loadStartDay() -> [StartDay]{
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "StartDay", predicate: predicate)
let operation = CKQueryOperation(query: query)
var startDays: [StartDay] = []
operation.desiredKeys = ["wakeUp", "wellSlept", "dNN", "recordID", "createdDato"]
operation.recordFetchedBlock = { (record:CKRecord) in
let newStartDay = StartDay()
newStartDay.wakeUp = record.object(forKey: "wakeUP") as? String
newStartDay.sleptWell = record.object(forKey: "sleptWell") as? String
newStartDay.dNN = record.object(forKey: "dNN") as? String
newStartDay.recordID = record.object(forKey: "recordID") as? CKRecord.ID
newStartDay.created = record.object(forKey: "createdDato") as? String
print(newStartDay.created)
startDays.append(newStartDay)
}
You can use print(startDayDates!) or print(startDayDates ?? "default value").
But I recommend usage of startDayList.compactMap() instead of startDayList.map()to ensure your array doesn't contain nil values.
You can also do like this:
startDayList
.compactMap { $0.created }
.forEach { print($0) }
As you designed the database model you exactly know which record attributes always exist. Declaring class properties as implicit unwrapped optional as an alibi not to write an initializer is very bad practice.
Assuming every attribute in a record does have a value declare the properties as non-optional and write an initializer.
At least created and recordID are supposed to have always a value!
import UIKit
import CloudKit
class StartDay {
var recordID: CKRecord.ID
var wakeUp: String
var sleptWell: String
var dNN: String
var created: String
init(record : CKRecord) {
// recordID can be retrieved directly
self.recordID = record.recordID
self.wakeUp = record.object(forKey: "wakeUP") as! String
self.sleptWell = record.object(forKey: "sleptWell") as! String
self.dNN = record.object(forKey: "dNN") as! String
self.created = record.object(forKey: "createdDato") as! String
}
}
and create instances with
operation.recordFetchedBlock = { record in
startDays.append(StartDay(record: record))
}
Now the Optional has gone.
print(startDayList.map{ $0.created })

Merge objects of the same type

Say I have a struct Coin
struct Coin {
var value: Float?
var country: String?
var color: String?
}
I have two instances of a Coin; we'll call them coinA and coinB.
let coinA = Coin()
coinA.value = nil
coinA.country = "USA"
coinA.color = "silver"
let coinB = Coin()
coinB.value = 50.0
Now, I want to merge the values of coinB into coinA. So the result would be coinA whose values would result in:
country = "USA"
color = "silver"
value = 50.0
I am able to accomplish this with Dictionary objects using the merge() function. However, I am unsure how to accomplish this using custom Swift objects. Is there a way?
Update
Here's how I've gotten it to work with dictionaries:
var originalDict = ["A": 1, "B": 2]
var newDict = ["B": 69, "C": 3]
originalDict.merge(newDict) { (_, new) in new }
//originalDict = ["A": 1, "B": 69, "C": 3]
And I will further clarify, in this function if the newDict does not have keys that the originalDict, the originalDict maintains them.
Ultimately, the most efficient way in the fewest lines of code is probably exactly what you'd expect:
extension Coin {
func merge(with: Coin) -> Coin {
var new = Coin()
new.value = value ?? with.value
new.country = country ?? with.country
new.color = color ?? with.color
return new
}
}
let coinC = coinA.merge(with: coinB)
Note that in the above scenario, the resulting value will always be coinA's, and will only be coinB's if coinA's value for a given key is nil. Whenever you change, add, or delete a property on Coin, you'll have to update this method, too. However, if you care more about future-proofing against property changes and don't care as much about writing more code and juggling data around into different types, you could have some fun with Codable:
struct Coin: Codable {
var value: Float?
var country: String?
var color: String?
func merge(with: Coin, uniquingKeysWith conflictResolver: (Any, Any) throws -> Any) throws -> Coin {
let encoder = JSONEncoder()
let selfData = try encoder.encode(self)
let withData = try encoder.encode(with)
var selfDict = try JSONSerialization.jsonObject(with: selfData) as! [String: Any]
let withDict = try JSONSerialization.jsonObject(with: withData) as! [String: Any]
try selfDict.merge(withDict, uniquingKeysWith: conflictResolver)
let final = try JSONSerialization.data(withJSONObject: selfDict)
return try JSONDecoder().decode(Coin.self, from: final)
}
}
With that solution, you can call merge on your struct like you would any dictionary, though note that it returns a new instance of Coin instead of mutating the current one:
let coinC = try coinA.merge(with: coinB) { (_, b) in b }
I thought it would be interesting to show a solution based on Swift key paths. This allows us to loop somewhat agnostically through the properties — that is, we do not have to hard-code their names in a series of successive statements:
struct Coin {
var value: Float?
var country: String?
var color: String?
}
let c1 = Coin(value:20, country:nil, color:"red")
let c2 = Coin(value:nil, country:"Uganda", color:nil)
var c3 = Coin(value:nil, country:nil, color:nil)
// ok, here we go
let arr = [\Coin.value, \Coin.country, \Coin.color]
for k in arr {
if let kk = k as? WritableKeyPath<Coin, Optional<Float>> {
c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
} else if let kk = k as? WritableKeyPath<Coin, Optional<String>> {
c3[keyPath:kk] = c1[keyPath:kk] ?? c2[keyPath:kk]
}
}
print(c3) // Coin(value: Optional(20.0), country: Optional("Uganda"), color: Optional("red"))
There are unfortunate features of key paths that require us to cast down from the array element explicitly to any possible real key path type, but it still has a certain elegance.
If you're willing to make the merge function specific to Coin, you can just use the coalesce operator like so:
struct Coin {
var value: Float?
var country: String?
var color: String?
func merge(_ other: Coin) -> Coin {
return Coin(value: other.value ?? self.value, country: other.country ?? self.country, color: other.color ?? self.color)
}
}
let coinC = coinA.merge(coinB)
This will return a new Coin using the values from coinB, and filling in any nils with those from coinA.
If your goal is to change coin A what you need is a mutating method. Note that structures are not like classes. If you would like to change its properties you need to declare your coin as variable. Note that none of your examples would compile if you declare your coins as constants:
struct Coin {
var value: Float?
var country: String?
var color: String?
mutating func merge(_ coin: Coin) {
value = value ?? coin.value
country = country ?? coin.country
color = color ?? coin.color
}
init(value: Float? = nil, country: String? = nil, color: String? = nil) {
self.value = value
self.country = country
self.color = color
}
}
Playground testing:
var coinA = Coin(country: "USA", color: "silver")
coinA.merge(Coin(value: 50))
print(coinA.country ?? "nil") // "USA"
print(coinA.color ?? "nil") // "silver"
print(coinA.value ?? "nil") // 50.0
This is not a high-level approach like the merge one you shared the link to but as long as you have a struct to implement the merge feature into, it will do the job.
func merge(other: Coin, keepTracksOfCurrentOnConflict: Bool) -> Coin {
var decidedValue = value
if decidedValue == nil && other.value != nil {
decidedValue = other.value
} else if other.value != nil {
//in this case, it's conflict.
if keepTracksOfCurrentOnConflict {
decidedValue = value
} else {
decidedValue = other.value
}
}
var resultCoin = Coin(value: decidedValue, country: nil, color: nil)
return resultCoin
}
}
You can do the same for other properties.
If you want to wrap it around protocol. The idea behind is the same:
you convert object's to dict
merge two dict's
convert merged dict back to your object
import Foundation
protocol Merge: Codable {
}
extension Dictionary where Key == String, Value == Any {
func mergeAndReplaceWith(object: [Key: Value]) -> [Key: Value] {
var origin = self
origin.merge(object) { (_, new) in
new
}
return origin
}
}
extension Merge {
func toJson() -> [String: Any] {
let jsonData = try! JSONEncoder().encode(self)
let json = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]
return json
}
func merge(object: Merge) -> Merge {
let origin = self.toJson()
let objJson = object.toJson()
let decoder = JSONDecoder()
let merge = origin.mergeAndReplaceWith(object: objJson)
var jsonData = try! JSONSerialization.data(withJSONObject: merge, options: .prettyPrinted)
var mergedObject = try! decoder.decode(Self.self, from: jsonData)
return mergedObject
}
}
struct List: Merge {
let a: String
}
struct Detail: Merge {
struct C: Codable {
let c: String
}
let a: String
let c: C?
}
let list = List(a: "a_list")
let detail_without_c = Detail(a: "a_detail_without_c", c: nil)
let detail = Detail(a: "a_detail", c: Detail.C(c: "val_c_0"))
print(detail.merge(object: list))
print(detail_without_c.merge(object: detail))
Detail(a: "a_list", c: Optional(__lldb_expr_5.Detail.C(c: "val_c_0")))
Detail(a: "a_detail", c: Optional(__lldb_expr_5.Detail.C(c: "val_c_0")))
With this solution you can actually merge two representations of your endpoint, in my case it is List and Detail.

Reflection in swift 2

I have a class User:
import UIKit
import ObjectMapper
class User: NSObject, CustomStringConvertible, Mappable {
var FirstName: NSString! ;
var LastName: NSString! ;
required init?(_ map: Map){
}
func mapping(map: Map) {
FirstName <- map["FirstName"]
LastName <- map["LastName"]
}
override var description:String {
var s:String=""
//USE REFLECTION TO GET NAME AND VALUE OF DATA MEMBERS
for var index=1; index<reflect(self).count; ++index {
s += (reflect(self)[index].0 + ": "+reflect(self)[index].1.summary+"\t")
}
return s
}
}
In swift 1.2, I was using reflect() method to get array of all the data members with their names and values.
Now, after I have updated to swift 2, I am getting the following error:
'reflect' is unavailable: call the 'Mirror(reflecting:)' initializer
With some trials, I was able to get the count of data members by this: Int(Mirror(reflecting: self).children.count), but still, I am unable to get the member name and its value.
I have looked into the following resources:
https://netguru.co/blog/reflection-swift
http://nshipster.com/mirrortype/
UPDATE
I have found the an answer here: https://stackoverflow.com/a/32846514/4959077. But this doesn't tell how to find out the type of reflected value. If the value is int and we parse it into String then it gives error.
You may access the reflected attribute "label" name, value and type as follows:
let mirror = Mirror(reflecting: SomeObject)
var dictionary = [String: Any]()
for child in mirror.children {
guard let key = child.label else { continue }
let value: Any = child.value
dictionary[key] = value
switch value {
case is Int: print("integer = \(anyValue)")
case is String: print("string = \(anyValue)")
default: print("other type = \(anyValue)")
}
switch value {
case let i as Int: print("• integer = \(i)")
case let s as String: print("• string = \(s)")
default: print("• other type = \(anyValue)")
}
if let i = value as? Int {
print("•• integer = \(i)")
}
}
Note: per the question followup, three approaches to determine the type of the reflected value are shown.
I have a solution that finds the name and type of a property given any class that inherits from NSObject.
I wrote a lengthy explanation on StackOverflow here, and my project is available here on Github,
In short you can do something like this (but really check out the code Github):
public class func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? {
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
var types: Dictionary<String, Any> = [:]
for i in 0..<Int(count) {
guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
let type = getTypeOf(property: property)
types[name] = type
}
free(properties)
return types
}

Guard when setting multiple class properties in Swift 2

It's trivial enough to do something like this:
class Collection {
init(json: [String: AnyObject]){
guard let id = json["id"] as? Int, name = json["name"] as? String else {
print("Oh noes, bad JSON!")
return
}
}
}
In that case we were using let to initialize local variables. However, modifying it to use class properties causes it to fail:
class Collection {
let id: Int
let name: String
init(json: [String: AnyObject]){
guard id = json["id"] as? Int, name = json["name"] as? String else {
print("Oh noes, bad JSON!")
return
}
}
}
It complains that let or var needs to be used but obviously that isn't the case. What's the proper way to do this in Swift 2?
In the if let, you are unwrapping values from the optional as new local variables. You can’t unwrap into existing variables. Instead, you have to unwrap, then assign i.e.
class Collection {
let id: Int
let name: String
init?(json: [String: AnyObject]){
// alternate type pattern matching syntax you might like to try
guard case let (id as Int, name as String) = (json["id"],json["name"])
else {
print("Oh noes, bad JSON!")
self.id = 0 // must assign to all values
self.name = "" // before returning nil
return nil
}
// now, assign those unwrapped values to self
self.id = id
self.name = name
}
}
This is not specific to class properties - you can’t conditionally bind into any variable, for example this doesn’t work:
var i = 0
let s = "1"
if i = Int(s) { // nope
}
Instead you need to do:
if let j = Int(s) {
i = j
}
(though of course, in this case you’d be better with let i = Int(s) ?? 0)

Swift error when trying to access Dictionary: `Could not find member 'subscript'`

This won't compile:
I've tried a couple different things; different ways of declaring the Dictionary, changing its type to match the nested-ness of the data. I also tried explicitly saying my Any was a collection so it could be subscripted. No dice.
import UIKit
import Foundation
class CurrencyManager {
var response = Dictionary<String,Any>()
var symbols = []
struct Static {
static var token : dispatch_once_t = 0
static var instance : CurrencyManager?
}
class var shared: CurrencyManager {
dispatch_once(&Static.token) { Static.instance = CurrencyManager() }
return Static.instance!
}
init(){
assert(Static.instance == nil, "Singleton already initialized!")
getRates()
}
func defaultCurrency() -> String {
let countryCode = NSLocale.currentLocale().objectForKey(NSLocaleCountryCode) as String
let codesToCountries :Dictionary = [ "US":"USD" ]
if let localCurrency = codesToCountries[countryCode]{
return localCurrency
}
return "USD"
}
func updateBadgeCurrency() {
let chanCurr = defaultCurrency()
var currVal :Float = valueForCurrency(chanCurr, exchange: "Coinbase")!
UIApplication.sharedApplication().applicationIconBadgeNumber = Int(currVal)
}
func getRates() {
//Network code here
valueForCurrency("", exchange: "")
}
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"][exchange][currency] as Float
}
}
Let's take a look at
response["current_rates"][exchange][currency]
response is declared as Dictionary<String,Any>(), so after the first subscript you try to call another two subscripts on an object of type Any.
Solution 1. Change the type of response to be a nested dictionary. Note that I added the question marks because anytime you access a dictionary item you get back an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String, Float>>>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"]?[exchange]?[currency]
}
Solution 2. Cast each level to a Dictionary as you parse. Make sure to still check if optional values exist.
var response = Dictionary<String,Any>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
let exchanges = response["current_rates"] as? Dictionary<String,Any>
let currencies = exchanges?[exchange] as? Dictionary<String,Any>
return currencies?[currency] as? Float
}
You can get nested dictionary data by following these steps:
let imageData: NSDictionary = userInfo["picture"]?["data"]? as NSDictionary
let profilePic = imageData["url"] as? String
func valueForCurrency(currency :String, exchange :String) -> Float? {
if let exchanges = response["current_rates"] as? Dictionary<String,Any> {
if let currencies = exchanges[exchange] as? Dictionary<String,Any> {
return currencies[currency] as? Float
}
}
return nil
}
response is declared as such:
var response = Dictionary<String,Any>()
So the compiler thinks response["current_rates"] will return an Any. Which may or may not be something that is subscript indexable.
You should be able to define you type with nested Dictionaries, 3 levels and eventually you get to a float. You also need to drill in with optional chaining since the dictionary may or may not have a value for that key, so it's subscript accessor returns an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String,Float>>>()
// ... populate dictionaries
println(response["current_rates"]?["a"]?["b"]) // The float