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

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

Related

Swift 5 storing and passing KeyPaths

Let's say I have the following class:
class User: NSObject {
var name = "Fred"
var age = 24
var email = "fred#freddy.com"
var married = false
}
I want to be able to write a generic function that takes in a list of KeyPaths for a known class type, read the values and print to screen. The problem is, the I can't get the following code to compile as the type of the KeyPath's Value is not known, and will be different for each time. What do I have to do to make this work generically?
Consider the following:
struct KeyPathProperties<T> {
var name: String
var relatedKeyPaths: [KeyPath<T, Any>]
}
extension KeyPath where Root == User {
var properties: KeyPathProperties<Root> {
switch self {
case \Root.name:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
default:
fatalError("Unknown key path")
}
}
}
This line fails to compile:
return KeyPathProperties(name: "name", relatedKeyPaths: [\Root.age, \Root.email])
with this error:
Cannot convert value of type 'KeyPath<User, Int>' to expected element type 'KeyPath<User, Any>'
This is what I wish to be able to do, for instance:
let myUser = User()
var keyPathProps = KeyPathProperties(name: "name", relatedKeyPaths: [\User.age, \User.email])
for keyPath in props.relatedKeyPaths {
print("Value: \(myUser[keyPath: keyPath])")
}
The above won't compile of course. Essentially I want to store keyPaths in an array at runtime, so I can generically at some point in time get values out of the User. I need to know if I can re-write the above in some way where the compiler can safely and correctly determine the type of the keyPath's value at runtime.
This is a conceptual use case for a much more complex architectural issue I'm trying to solve with hopefully less code.
MORE INFORMATION:
At runtime I wish to keep track of the properties that get modified - these properties are held in a modifiedProps array in each object / instance. At some point at runtime, I wish to be able to enumerate over this array of KeyPaths and print their values like so:
for modifiedKeyPath in self.modifiedProps {
print ("\(self[keyPath: modifiedKeyPath])"
}
In short - I need to be able to capture the generic type of the KeyPath within KeyPathProperties. How do I achieve this?
SIDE NOTE: I can already easily achieve this by using Swift 3 style string based KeyPaths (by adding #objc to the class properties). I can store an array of keyPaths as strings and later do:
let someKeyPath = #keyPath(User.email)
...
myUser.value(forKeyPath: someKeyPath)
I just cannot do this with Swift 4 KeyPaths generically.
The error tells you what your misconception is:
Cannot convert value of type 'KeyPath<User, Int>'
to expected element type 'KeyPath<User, Any>'
You seem to think that you can use a KeyPath<User, Int> where a KeyPath<User, Any> is expected, ostensibly on the grounds that an Int is an Any. But that's not true. These are generic types, and generic types are not covariant — that is, there is no substitution principle for generics based on their parameterized types. The two types are effectively unrelated.
If you need an array of key paths regardless of their parameterized types, you would need an array of PartialKeyPath or AnyKeyPath. It seems that in your use case the root object is the same throughout, so presumably you want PartialKeyPath.

String property returning different values when used directly or in string interpolation

I've just finished debugging a situation where some of my labels were not displaying any text, despite the string in question definitely containing a value (it was printing the line before). I eventually pinned it down to returning different values depending on the way I accessed the string. Please note I'm not looking for ways to work around this, I am looking for a reason I am missing as to why this behaviour happens.
I had a protocol with the following optional String property, set to default to nil:
protocol SomeProtocol {
var titleString: String? { get }
}
extension SomeProtocol {
var titleString: String? {
return nil
}
}
Which was implemented in a class with a not optional String, with title set elsewhere:
class SomeClass: SomeProtocol {
var title: String
var titleString: String {
return title
}
}
When attempting to access the value of titleString from a SomeClass object, it always returned nil, regardless of the title property. This was seen through assigning to a label like:
label.text = someClassInstance.titleString
However, when I printed the value or set the label through
label.text = "\(someClassInstance.titleString)"
everything worked, and it displayed the text.
I've narrowed the source of this down to where I've overridden the optional property with one not optional. Clearly when I access the property directly it is returned by the protocol implementation, while using it by string interpolation returns the class one. What is actually behind this behaviour?
Edit: to demonstrate this behaviour run this gist in an Xcode playground:
https://gist.github.com/CaileanWilkinson/357c17f36d04b522b9bcf1241a825d9f
I feel like this is somewhat similar to the solution of this question of mine. In that question, there is also two almost identical properties - one optional, the other non-optional. And I experienced a similar situation where Swift can't figure out which property I want.
Your titleString in SomeClass is not overriding the titleString property in the protocol. This is reflected in Xcode's suggestions:
You can access both properties like this:
someObject.titleString as String // accesses the one in SomeClass
someObject.titleString as String? // accesses the one in the protocol
My point here is that the type of the expression matters. If the type of expression swift expects is String, then it resolves to the one in SomeClass. If the expected type of the expression is String?, then it evaluates to the one in the protocol.
This explains why setting the label's text without string interpolation will call the property in the protocol (label.text is String?, so it expects a String?) and why using string interpolation will call the property in SomeClass (String interpolation expects a non-optional).

Conditional Defining a property in model object

I have a model object and there are some properties in that object. Based on some conditions, I want a property to be defined there or not to be defined. For example, this property is my app version.
class Person {
var name: String
var address: String
var age: String
// I want some condition here like if myAppVersion > 1.0 then add isChild
// property to my model object other wise don't add that
var isChild: Bool
// Normal property again
var gender: String
}
I want this behaviour because the properties are coming from the backend and all these properties are required, so if, for some reason, the BE doesn't send the a required property which the client is expecting, then I will crash. These properties have to be mandatory and not optional.
Don't do this.
Declare your parameter as an optional and set it to nil if you don't want it to have a value. You should create two separate classes if you want to have different implementations, but that would be pretty superfluous for just one little change.
If your application crashes just because a property has a nil value, you should really take a look at optional handling in Swift and nullability in Objective-C.

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: get the compile time name of variable (referencing to a class)

Is there a way to get the compile time name of a variable in Swift 2?
I mean the first variable name, which references to a new class instance, if any.
Here is a simple example:
public class Parameter : FloatLiteralConvertible {
var name:String?
var value:Double
// init from float literal
public required init (floatLiteral value: FloatLiteralType) {
self.value = Double(value)
self.name = getLiteralName()
}
func getLiteralName () -> String {
var literalName:String = ""
// do some magic to return the name
return literalName
}
}
let x:Parameter = 2.0
print(x.value) // this returns "2.0"
print(x.name!) // I want this to return "x"
I've already checked similar questions on that topic handling mirroring or objective-c reflections. But in all those cases, one can get only the property names in a class - in the example above name and value.
The same question has been asked in 2014 - Swift: Get Variable Actual Name as String
- and I hope, that since then there is a solution in swift 2.
No, there is no way to do that.
You have to understand that in the compiled state that variable usually does not exist. It can be optimized out or it is represented only as an item on the execution stack.
Even in languages with much better reflection that Swift has, usually you cannot inspect local variables.
To be honest, getting the name of a local variable dynamically has no practical use case.