Is it possible in Swift to create a struct that has "dynamic" properties?
i.e. if I have a dictionary that is returned from some JSON API and I know at build time that it has name, id in it.
Then I can build a struct like...
struct MyStruct {
let name: String
let id: String
}
But then if the dictionary has a custom attribute like age... can I do something to add a typed property to the struct at run time?
I know (think) this sort of thing was possible with Objective-C but there was a lot more runtime control.
Is it possible in Swift?
Thanks
Simply add a variable to your class that is optional, as follows:
var age: Int?
Hope that helps
Related
Is there a way to put multiple values in one key in Swift's dictionary, such as 'multimap' in Java?
var multiDic = [String:UserData]()
This is the dictionary I declared. it has this structure.
struct UserData{
var name: String
var id: String
var nickname: String
}
And when I put the value in one key in the dictionary, the value is updated without being stacked.
for key in 1...10 {
multiDic.updateValue(UserData(name:"name+\(key)", id:"id+\(key)", nickname:"nick+\(key)"), forKey: "A")
}
print(multiDic.count)
reults
1
How can I accumulate multiple values on a single key? I looked up a library called 'Buckets'
(https://github.com/mauriciosantos/Buckets-Swift#buckets), but there has been no update since swift 3.0.
Swift dictionary doesn't support this. One work around is to declare the dictionary to hold an array of values, like this:
var multiDic = [String:[UserData]]()
You would have to modify your code for adding values to work with the array instead of updating the value in place.
I haven't tried the "Buckets" project you mention, but for what it's worth, it probably still works either as-is or with minor changes.
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
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)
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.
I think I have programmed myself into a corner, but I'm hoping you all know a way out. I have a class...
class Card {
var order: Int? = -1
var tag: String = "0"
var comment: String?
var data : [String: NSNumber]
}
Ideally everything would be in data, which is a few strings and lots of numbers. I started with [String, String] but I found I was writing lots of code to cast and convert when I wanted to (say) compare one of those numbers to zero. Changing it to [String, NSNumber] simplified all that code, but now my tableViewDataSource becomes very complex because some of the data is in data and some is in a separate property like comment. I even tried [String, Any], but then everything had to be cast all the time to do anything.
I have a feeling I am missing something fundamental here. When working with NSTableViews, is there a simple way to use Swift properties that I'm missing? valueForKey: does not work, there's no easy way to do a reflection-like solution I know of, etc. Any suggestions?
You can only bind dynamic properties, and your class needs to inherit from NSObject or implement NSObjectProtocol. Additionally, nilable value-types aren't allowed, so you cannot bind Int?
ie.:
class Card: NSObject {
dynamic var order: Int = -1
dynamic var tag: String = "0"
dynamic var comment: String?
dynamic var data: [String: NSNumber]
}