Check if variable is computed or stored - swift

In my app I translate objects from custom classes into dictionaries so that they can be saved locally in a plist as well as on a server. I use the following to turn the properties of a class into a dictionary:
func dictionary() -> [String : Any] {
var count: UInt32 = 0;
let myClass: AnyClass = self.classForCoder;
let properties = class_copyPropertyList(myClass, &count);
var dictionaryRepresentation: [String:Any] = [:]
for i in 0..<count {
let property = properties![Int(i)]
let cStringKey = property_getName(property);
let key = String(cString: cStringKey!)
dictionaryRepresentation[key] = self.value(forKey: key) as Any
}
return dictionaryRepresentation
}
I have a problem, however, with computed properties. It seems that those are computed and the returned value gets put into the dictionary as well, which I would like to avoid. So here is my question:
Is it possible to check whether is a property computed programatically using only its name?
I am assuming this could be possible by trying to assign a value to it which would give me an error or some similar approach.

Here is what seems to be a working solution, based on suggestion by dasblinkenlight.
Rather than using the Objective-C method outlined above, create a Mirror of the class which has a children made up of all settable properties, therefore excluding computables.
Used like this:
let mirror = Mirror(reflecting: MyObject)
for case let (label?, value) in mirror.children {
print (label, value)
}
Here label is the name of the variable and value is obviously the value.
EDIT: In case anyone wants to convert objects into dictionary, I am posting the full code here as well. Do however remember that if values are custom objects as well, those will need to be converted too.
func dictionary() -> [String:Any] {
let mirror = Mirror(reflecting: self)
var dictionaryRepresentation = [String:Any]()
for case let (label, value) in mirror.children {
guard let key = label else { continue }
dictionaryRepresentation[key] = value
}
return dictionaryRepresentation
}

You can try property_copyAttributeList(_:_:) function, it may contain a read-only marker for swift's computed properties. Although I guess let properties also will have that marker, so you must find a way to differ them.

Related

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

Use of undeclared type 'valueMirror' when using Mirror

I am trying to map a struct to other class that have same properties. but it keep showing this error
Use of undeclared type 'valueMirror'
My code
extension Mapper {
func map<T:Object>(to type: T.Type){
let object = T()
let m = Mirror(reflecting: self)
for property in m.children {
guard let key = property.label else { continue }
let value = property.value
let valueMirror = Mirror(reflecting: value)
if valueMirror.displayStyle == .collection {
let array = value as! valueMirror.subjectType // <-- error
object.setValue(array.asRealMList, forKey: key)
} else {
object.setValue(value, forKey: key)
}
}
}
}
valueMirror.subjectType is not a type as far as the compiler is concerned. There must be a compile time type after as!.
Since the only place you are using array is in array.asRealMList, you probably just need to cast value to a type that has the property asRealMList. As you said in the comments, this is an extension on Array.
Luckily Array is covariant, so even without knowing which type of array it is, you'll be able to cast any array to [Any]:
let array = value as! [Any]
valueMirror.subjectType is of type Any.Type.
You probably want to cast value to Any.

Optional Any? equals to nil

I have:
let value: Any? = myObject[keyPath: myParamKeyPath]
where myParamKeyPath refers to a String?.
Then, when myParam is supposed to be nil, I have:
value == nil returns false
(value as? String?) == nil returns true
Is it possible to check if value equals nil without having to cast it to a String? in the first place? Something like comparing it to NSNull maybe?
Also, I can't change the value type to String? directly as it is also used for other type in my code.
EDIT:
(value as? String?) == nil returns true is irrelevant indeed.
But I can still go print my value pointed by the keypath and it will actually be nil. So I still don't get why value == nil returns false when Im expecting it to be true...
EDIT2 with more code:
let setting = appSettings.settings[indexPath.row]
let value = appSettings[keyPath: setting.keyPath]
let fontAwesome: FontAwesome
switch setting.keyPath {
case \PrinterSettings.printableImageIDs:
fontAwesome = blablabla
case \WeatherSettings.lockscreenImageIDs:
fontAwesome = blablabla
default:
if let value = value as? FakeButtonPlacementSubSettings {
fontAwesome = blablabla
} else {
fontAwesome = value != nil ? .checkSquareO : .squareO
}
}
I am expecting to get fontAwesomeIcon = .squareO but I am getting checkSquareO when the value pointed by myObject[keyPath: myParamKeyPath] is String? (it does the same for another value which is a Bool? later on).
I must be missing something somewhere...
EDIT 3 screenshot:
EDIT 4 more clarification of what I'm trying to do here:
First, thank you again for your help if you get there.
Here are 2 photos about my project current design:
I am using with KVO in my project. I was previously using the objective-c string #keyPath for this. It was working great, but almost all my model had to be converted in #objc. So my current goal here is to remove it and switch to the new Swift 4 keypath system.
To resume: I have a user class containing lot of settings (more than on the screenshot) which can be of several type (and some custom types also).
On the other hand, I have created around 10 "setting editor view controllers": one by type of settings. I would like to use the same setting editor VC to edit each one of the same type of settings.
For example, if the setting to edit is a boolean, I would like to use the "boolean editor VC" and get my selected setting edited by the user's new choice. This is why I am using KVO system. I'm not willing to keep it if you guys have a better solution for this. It would then also get rid of my Any? == nil issue.
How about the following?
struct S {
let x: String?
}
let v1: Any? = S(x: "abc")[keyPath: \S.x]
print(v1 is String? && v1 == nil) // false
let v2: Any? = S(x: nil)[keyPath: \S.x]
print(v2 is String? && v2 == nil) // true
The issue is that your code is assuming the value is String?, whereas it is really String??. I believe this stems from the use of AnyKeyPath:
struct Foo {
let bar: String?
}
let foo = Foo(bar: "baz")
let keyPath: AnyKeyPath = \Foo.bar
print(foo[keyPath: keyPath]) // Optional(Optional("baz"))
let keyPath2 = \Foo.bar
print(foo[keyPath: keyPath2]) // Optional("baz")
I wonder if there is some way to adapt your code to use KeyPath<T, String?> pattern, so that, as you say, your myParamKeyPath really does explicitly refer to a String?, .e.g.:
let foo = Foo(bar: "baz")
let keyPath = \Foo.bar
func keyPathExperiment(object: Any, keyPath: AnyKeyPath) {
print(object[keyPath: keyPath])
}
func keyPathExperiment2<T>(object: T, keyPath: KeyPath<T, String?>) {
print(object[keyPath: keyPath])
}
keyPathExperiment(object: foo, keyPath: keyPath) // Sigh: Optional(Optional("baz"))
keyPathExperiment2(object: foo, keyPath: keyPath) // Good: Optional("baz")
There's not enough in your code snippet for us to see how precisely this might be adapted to your situation.
As Rob Napier said, I wonder if there might be Swiftier approaches to the problem. E.g. when I see switch statements like this, I might tend to gravitate towards enum pattern.

Variable used within its own initial value Swift 3

I try to convert my code to swift 3 an I have spent hours on the following error:
Type 'Any' has no subscript members
Here's was my original code:
let data: AnyObject = user.object(forKey: "profilePicture")![0]
I looked at the answers here but I'm still stuck. (I do programming as a hobby, I'm not a pro :/)
I've try that:
let object = object.object(forKey: "profilePicture") as? NSDictionary
let data: AnyObject = object![0] as AnyObject
But now I get this error:
Variable used within its own initial value
Second issue: Use always a different variable name as the method name, basically use more descriptive names than object anyway.
First issue: Tell the compiler the type of the value for profilePicture, apparently an array.
if let profilePictures = user["profilePicture"] as? [[String:Any]], !profilePictures.isEmpty {
let data = profilePictures[0]
}
However, the array might contain Data objects, if so use
if let profilePictures = user["profilePicture"] as? [Data], !profilePictures.isEmpty {
let data = profilePictures[0]
}
Or – what the key implies – the value for profilePicture is a single object, who knows (but you ...)
And finally, as always, don't use NSArray / NSDictionary in Swift.

How to check if a value is nil or not from a Set<T>

I have a Set and now I'm trying to get an object in a index position:
var testItem: ExpandableCell
if let object = expandableCells[indexPath.row] {
testItem = object as! ExpandableCell
}
But I get an error.
UPDATE:
I just convert from SET to Array my collection class variable and now I have this:
let realItem : ExpandableCell?
if let testItem: AnyObject = expandableCells[indexPath.row] {
realItem = testItem as! ExpandableCell
print(realItem?.title)
}
It compiles without a problem but when i run the code and the index its outside of its boundaries then I get the following error:
So seems that my if let statement is not working.
Any clue?
Set is an unordered collection of unique objects. Because they're unordered, getting them by an Int index makes no sense.
Perhaps you meant to use an Array, an ordered collection of objects. Array doesn't guarantee the uniqueness of its objects, but it does preserver ordering, thus Int indexes make sense.
you can use loop Instead if/let condition
for testItem in expandableCells {
if let realItem = testItem as? ExpandableCell {
print(realItem?.title)
}
}