Get codable's property names as strings in swift - swift

Edit
let disucssionMessageTimestampKey = DiscussionMessage.CodingKeys.messageTimestamp.stringValue gives an error:
'CodingKeys' is inaccessible due to 'private' protection level
I have a message structure defined like this:
struct DiscussionMessage: Codable {
let message, userCountryCode, userCountryEmoji, userName, userEmailAddress: String
let messageTimestamp: Double
let fcmToken, question, recordingUrl, profilePictureUrl: String?
}
I want to define a variable disucssionMessageTimestampKey whose value will be messageTimestamp. I want to use disucssionMessageTimestampKey variable in the following query:
messagesReference.queryOrdered(byChild: "messageTimestamp").queryStarting(atValue: NSDate().timeIntervalSince1970).observe(.childAdded)
So that I don't have to hardcode the string value ("messageTimestamp") of the variable name.
Now I know I could just do let disucssionMessageTimestampKey: String = "messageTimestamp". But this is again prone to errors. So I was wondering if there was a way that I could get the string value messageTimestamp without having to define it anywhere.
By something like this (I know this won't work but just to give an idea of what I am looking for)
let disucssionMessageTimestampKey: String = String(describing: DiscussionMessage.messageTimestamp) // Will store disucssionMessageTimestampKey = "messageTimestamp"
Also, would it be possible to completely define the key values first as strings and then use those as variable names in the actual codable object? I.e. first define let disucssionMessageTimestampKey: String = "messageTimestamp", and then use the variable disucssionMessageTimestampKey to define what the property (messageTimestamp) of the codable object should be called. (This is low priority but curious and seems related to the question at hand)

Related

Is there a way to get the name of a key path as a string? [duplicate]

How can you get a string value from Swift 4 smart keypaths syntax (e.g., \Foo.bar)? At this point I'm curious about any way at all, does not matter if it's complicated.
I like the idea of type information being associated with smart key path. But not all APIs and 3rd parties are there yet.
There's old way of getting string for property name with compile-time validation by #keyPath(). With Swift 4 to use #keyPath() you have to declare a property as #objc, which is something I'd prefer to avoid.
A bit late to the party, but I've stumbled upon a way of getting a key path string from NSObject subclasses at least:
NSExpression(forKeyPath: \UIView.bounds).keyPath
Short answer: you can't. The KeyPath abstraction is designed to encapsulate a potentially nested property key path from a given root type. As such, exporting a single String value might not make sense in the general case.
For instance, should the hypothetically exported string be interpreted as a property of the root type or a member of one of its nested types? At the very least a string array-ish would need to be exported to address such scenarios...
Per type workaround. Having said that, given that KeyPath conforms to the Equatable protocol, you can provide a custom, per type solution yourself. For instance:
struct Auth {
var email: String
var password: String
}
struct User {
var name: String
var auth: Auth
}
provide an extension for User-based key paths:
extension PartialKeyPath where Root == User {
var stringValue: String {
switch self {
case \User.name: return "name"
case \User.auth: return "auth"
case \User.auth.email: return "auth.email"
case \User.auth.password: return "auth.password"
default: fatalError("Unexpected key path")
}
}
usage:
let name: KeyPath<User, String> = \User.name
let email: KeyPath<User, String> = \User.auth.email
print(name.stringValue) /* name */
print(email.stringValue) /* auth.email */
I wouldn't really recommend this solution for production code, given the somewhat high maintenance, etc. But since you were curious this, at least, gives you a way forward ;)
For Objective-C properties on Objective-C classes, you can use the _kvcKeyPathString property to get it.
However, Swift key paths may not have String equivalents. It is a stated objective of Swift key paths that they do not require field names to be included in the executable. It's possible that a key path could be represented as a sequence of offsets of fields to get, or closures to call on an object.
Of course, this directly conflicts with your own objective of avoiding to declare properties #objc. I believe that there is no built-in facility to do what you want to do.
Expanding on #Andy Heard's answer we could extend KeyPath to have a computed property, like this:
extension KeyPath where Root: NSObject {
var stringValue: String {
NSExpression(forKeyPath: self).keyPath
}
}
// Usage
let stringValue = (\Foo.bar).stringValue
print(stringValue) // prints "bar"
I needed to do this recently and I wanted to ensure that I get a static type check from the compiler without hardcoding the property name.
If your property is exposed to Objective-C(i.e #objc), you can use the #keyPath string expression. For example, you can do the following:
#keyPath(Foo.bar)
#keyPath(CALayer.postion)
See Docs

String as Member Name in Swift

I have an array of strings and a CoreData object with a bunch of variables stored in it; the strings represent each stored variable. I want to show the value of each of the variables in a list. However, I cannot find a way to fetch all variables from a coredata object, and so instead I'm trying to use the following code.
ListView: View{
//I call this view from another one and pass in the object.
let object: Object
//I have a bunch of strings for each variable, this is just a few of them
let strings = ["first_name", "_last_name", "middle_initial" ...]
var body: some View{
List{
ForEach(strings){ str in
//Want to pass in string here as property name
object.str
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
}
}
}
So as you can see, I just want to pass in the string name as a member name for the CoreData object. When I try the code above, I get the following errors: Value of type 'Object' has no member 'name' and Expected member name following '.'. Please tell me how to pass in the string as a property name.
CoreData is heavily based on KVC (Key-Value Coding) so you can use key paths which is much more reliable than string literals.
let paths : [KeyPath<Object,String>] = [\.first_name, \.last_name, \.middle_initial]
...
ForEach(paths, id: \.self){ path in
Text(object[keyPath: path]))
}
Swift is a strongly typed language, and iterating in a python/javascript like approach is less common and less recommended.
Having said that, to my best knowledge you have three ways to tackle this issue.
First, I'd suggest encoding the CoreData model into a dictionary [String: Any] or [String: String] - then you can keep the same approach you wanted - iterate over the property names array and get them as follow:
let dic = object.asDictionary()
ForEach(strings){ str in
//Want to pass in string here as property name
let propertyValue = dic[str]
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
Make sure to comply with Encodable and to have this extension
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
Second, you can hard coded the properties and if/else/switch over them in the loop
ForEach(strings){ str in
//Want to pass in string here as property name
switch str {
case "first_name":
// Do what is needed
}
}
Third, and last, You can read and use a technique called reflection, which is the closest thing to what you want to achieve
link1
link2

Principles behind when to use an optional type in Swift?

When designing classes/structs in Swift, what are the best practices for identifying when you should use an Optional type?
For example, lets say I'm mapping a JSON response to a Photo object and there is a property photographerName which is just a string. Sometimes we receive empty strings for the field photographerName by the response.
Should my Photo object use a String? type and assign it to nil? Or use a String type and set it to an empty string?
It really depends on the application architecture and data structures that you going to use in the application.
There is no rule that will describe how properly use optional chaining in the application architecture.
At first, we should know what is optional chaining.
Optional chaining is a process for querying and calling properties,
methods, and subscripts on an optional that might currently be nil. If
the optional contains a value, the property, method, or subscript call
succeeds; if the optional is nil, the property, method, or subscript
call returns nil. Multiple queries can be chained together, and the
entire chain fails gracefully if any link in the chain is nil.
Based on my experience, I am using optional values in my data structure when I clearly could not define if my model could or could not have some relationship or property.
Example:
struct User {
let name: String
let email: String
var friends: [String]?
init(name: String, email: String) {
self.name = name
self.email = email
}
}
let user = User(name: "Oleg", email: "Oleg#email.com")
I know that user will have name and email but I do not know if he would have any friends. Also, in this way, I could prevent my code from execution of operation on nil objects.
In case that you describe it really depends on the architecture of the application and requirements. For example, should we show placeholder if photographerName is empty or not? Are any additional operation that are using photographerName property?
It´s up to you what you like to use, but if you receive a response with a property that can be nil then you should use optional and handle it in the code.
Something like this:
struct Photo {
let photographerName: String?
init(photographerName: String? = nil) {
self.photographerName = photographerName
}
}
And then in your code to check if photographerName has a value:
if let photographerName = photo.photographerName {
// use photographerName in here
}

Swift: if is let redundancy

I just joined a project that has a lot of existing code. The previous programmer was perhaps unfamiliar with Swift or began development in the early stages of the Swift language. They seemed to be using the if let statement in an odd way. They seemed to want to use the statement as a if is let. Before I edit the code I would like to know if there is any valid use for this:
// In JSON parser
if value is String, let string = value as? String {
document.createdBy = string
}
First checking if value is of type String seems redundant to me. Doesn't Swift check for this in the let string = value as? String portion of the statement?
QUESTION
Why would this need to be checked twice? Or would there be a reason for this?
You're correct, this is redundant. If value is not a string, then value as? String would return nil, and the conditional binding would fail.
To check the type, and not use the casted result:
if value is String {
// Do something that doesn't require `value` as a string
}
To check the type and use the result:
if let value = value as? String { // The new name can shadow the old name
document.createdBy = value
}
Doing both makes no sense.

Difference between "\(string)" and string?

callfunc(string: "\(string)")
callfunc(string: string)
I am calling the same function with same string value but different approach....
let me know what is the difference in it? and also I want to know in terms of memory consumption.
there is no difference, "\()" is used if your string is something like
let someInt: Int = 20
print("my integer is \(someInt)") //"my integer is 20"
i.e. not String in first place.
there is no memory difference because String in Swift is not reference type, it is Struct, so you pass copy of string to your callfunc, not reference to it.
There's a difference when your string is implicitly unwrapped optional. Consider example:
func some(string: String)
{
print(string)
}
let string: String! = "s"
some(string: string)
some(string: "\(string)")
The output will be:
s
Optional("s")
callfunc(string: string)
In the above syntax its a normal function call with a string.
callfunc(string: "(string)")
But here when we will pass "(string)" as a parameter, internally "(string)" creates a new string and pass it as a parameter. So in that particular point of time the memory will go high because of memory allocation for the string, which will again deallocated immediately.
Normally you won't be able to observe it with a small string, but if you will convert an image into base64 and try to pass it as a string. you can able to see the difference.
Apart from that there is no difference in functionality.