Accessing all class property values dynamically - ios

So basically what I'm trying to do is list all properties of an object in a tableview in a key = value format.
But I want this to be independent of future changes to the object, so that if a new property is added later, no changes will be needed to my code.
So far I can access the properties via Mirror, but I run into problems when I'm trying to access the properties via value(forKey:), even though the class is inheriting NSObject it crashes with:
this class is not key value coding-compliant for the key
Some properties work while others don't, which I'm guessing is down to some of them being private and others #objc variables?
So is there any way to pre-validate that a key (property) can be accessed via value(forKey:) - so it doesn't end in a crash, so I if nothing else can show the values of the accessable properties?
Better yet, is there another way of accessing all properties and values of a given object in a dynamic way? (handling later additions of properties)
Code snippet:
let properties = Mirror(reflecting: currentUser).children.compactMap { $0.label }
if properties.count > 0 {
for property in properties {
if let test = currentUser[property] {
newData.append( (name: property, value: currentUser.value( forKey: property ).debugDescription) )
}
}
}

What you might be able to do is make your object conform to Encodable. That way, you could simply convert it into JSON (or some other format), then make your table view dynamically pull out all the keys and values from the JSON.
As long as any future changes to the object don’t break Encodable compliance, the table view will still be able to parse whatever JSON it receives.

Related

RLMArray object not stored with its object

I've a very weird problem in RealmSwift.
I've the following property in a realm object class called Device.
class Device: Object {
....
dynamic var name: String = ""
var services: List<Service> = List<Service>()
}
The issue is that when trying to fill this list and save the Device object, the service list is not saved.
While debugging I used the following to test
print(device)
Which prints the objects without any service object.
and
print(device.services)
Which prints all services objects.
I know it's weird, but I cannot save the object with its list object, although I can save any normal property in the device object like name property.
Any idea what is happening here?
What you are describing could happen if you are directly assigning to the services property. This is not supported, and List properties should always be declared as let.

RxSwift - Class property's binding

I have an question about binding:
I have an array of objects of my custom class: Array. Every object can be updated (change his properties value) in bg.
Also I have separated Controller, which take and store one object from list as variable and can update it (object still the same, so in list it will be updated too)
Is there any way to bind all object.property -> UILabels on Controller in way, when property changes automatically call label update?
Of course, there are multiple ways how to do it, but from your description I would use some kind of subject (because u said there will be changes in background so you will probably need hot observable )....For example Variable or PublishSubject. So you can crate
let myArrayStream: Variable<[MyObject]> = Variable([])
you can pass this variable as dependency to wherever you want, on one side you can subscribe to it, on the other side you can update it's value.

Managed property of type NSNumber? not initialized to nil

My Swift 3 app uses CoreData. Since the transition to Swift3/Xcode 8, I have abandoned automatic code generation for NSManagedObject subclasses (The new behavior, I still haven't quite figured it out yet), so I just specify non/manual and create my own classes. So far so good.
I am storing all my scalar attributes in NSNumber? properties, not optional scalar types like Int?, Double?, etc. (I'm not sure if optional scalar properties work well with Core Data...?).
The problem is, I can not detect the "Value not Yet Set" state of the attributes in my freshly created objects.
For example, I have:
class MyClass: NSManagedObject {
// Stores a double
#NSManaged var length: NSNumber?
}
Whenever I try to access the value, I never get nil, but 0.0 instead:
if let value = myObject.length?.doubleValue {
// Always gets executes and value 0.0 is unwrapped
// for new objects
} else {
// Never gets executed
}
However, if I manually set it to nil on instance insertion, i.e.:
override awakeFromInsert(){
super.awakeFromInsert()
// Explicitly set to nil:
self.length = nil
}
...then, I get the expected behavior.
Did I miss something?
I'm guessing that the #NSManaged attribute makes it in many ways different than a run-of-the-mill swift optional property... But I can not find mention of this behavior anywhere.
I agree with Arun that you need to select a type (in fact, you should be getting an error if you don't).
As to your issue, by default CoreData adds defaults for numeric fields. You can, however direct it to not set a default value by using the Data Model inspector, as shown below:
If you do turn off the default and leave the field empty when you create a new record, be sure the Optional checkbox is checked as well (it's a few rows above the default checkbox).
Also, if you add required fields without defaults after your first deployment, you will need to handle migration yourself as light migrations rely on the default when adding new mandatory fields to existing databases.
You cannot select NSNumber as datatype while selecting from the core-data .xcmodel class. When you create attribute you define a default value. Below is the screenshot which mentions the default value type .
So if you want the value in the length as double select double as default value.
Create managed class from the Xcode. Xcode->Editor->Create NSManagedObject SubClass. You generate the classes automatically.

Best way to store a single instance of RLMObject

I'm currently developing an iOS app with Realm as the backend data storage. I have a class that is an RLMObject for the user profile. It stores their name, profile picture, stats, etc.
There should only always be one of these objects, however I know implementing a singleton pattern is usually a bad idea. Currently I have it implemented as
//AppDelegate.swift, applicationDidFinishLaunching
//If there's no user profiles yet (first time launch), create one
if UserProfile.allObjects().count == 0 {
let realm = RLMRealm.defaultRealm()
try! realm.transactionWithBlock() {
realm.addObject(UserProfile())
}
}
//ProfileViewController.swift
//Get the first UserProfile
var userProfile: UserProfile? {
get {
return UserProfile.allObjects().objectAtIndex(0) as? UserProfile
}
}
Is there a better way to keep track of a single instance of this class?
Your code sample uses a computed property, which will fetch the object from the Realm each time you access it.
Instead, try using a lazy var property:
lazy var userProfile: UserProfile? = {
return UserProfile.allObjects().objectAtIndex(0) as? UserProfile
}()
This type of property will load the object from the Realm only the first time it is accessed. All subsequent accesses will be directly to the object.
Note that, since UserProfile is a Realm object, its fields will automatically update in response to changes made to the underlying object in the Realm. Likewise, any changes you wish to make will need to be wrapped within a Realm write transaction.
In terms of your overall architecture, there is nothing wrong with storing a single instance of an object type in a Realm similar to what you are doing. You may want to give your UserProfile object a hardcoded constant primary key, then use the 'add or update' version of the update API (see https://realm.io/docs/swift/latest/#updating-objects). This will allow you to avoid having to explicitly create a new object.

How to define a Fetched Property in an NSManagedObject Subclass

In a project I am working on, we have multiple persistent stores and fetched properties are defined on the Entities to provide access to the objects that live in the different stores.
When I run Editor -> Create NSManagedObject Subclass, the fetched properties do not get populated in the subclass, and therefore are not accessible in the different controllers that use this Entity.
My curiosity is how to define these objects in the subclass so that they can be used.
For example imagine I have some object below called "Some Object", and this object has a fetched property on it called "imageFile" (The File object lives in a different store so cannot be referenced directly)
class SomeObject: NSManagedObject {
#NSManaged var name: String
#NSManaged var id: String
#NSManaged var imageID: String
#NSManaged var imageFile: File //Not generated automatically like the rest
}
Unfortunately the above attempt fails with the following error:
unrecognized selector sent to instance 0x60800865de50
So my question is a nutshell, is how do you access Fetched Properties, or what is the syntax to reference them.
Please no answers saying "Don't use Fetched Properties" or "Use only one persistent store". I already know how to use normal relationships and want to know how to utilize this feature of Core Data. Thanks in advance!
UPDATE
Trying some of the solution's posted below I ran into some interesting information that may help. I printed out the object using "po someObject" and was suprised to see the following in the output under the data attribute:
imageFile = "<relationship fault: 0x618000043930 'imageFile'>";
imageID = "some Id"
However when trying to access imageFile using someObject.imageFile I am unable to access it. Using the valueForKey["imageID"] I am able to get a reference, but it fails on the cast to File every time. When printing the object out I get:
Optional(Relationship fault for (<NSFetchedPropertyDescription: 0x6180000e1780>), name imageFile, isOptional 1, isTransient 1, entity SomeObject...
Final Update
the valueForKey["imageID"] will trigger the fault and fetch the property, I had the attributes flipped in my xcdatamodelid file, and thats why it wasn't finding it at first.
In Objective-C you could define a #dynamic property file in a
category on SomeObject, but something similar does not exist in Swift
(as far as I know).
So the only possibility is to use Key-Value coding to retrieve the
fetched property (which is always represented as an array):
if let files = yourObject.valueForKey("imageFile") as? [File] {
// ...
}
Of course you can wrap this into a computed property as suggested
in #gutenmorgenuhu's answer.
If you want to add this to the same class as the NSManagedObject, you can use the extensions feature:
extension SomeObject{
var imageFile: String {
get {// Code to return your fetchedProperty
}
}
}

Resources