dynamic setting and getting values from Swift class - swift

I'd like to copy all properties from a NSManagedObject over to a "regular" Swift class. I don't want to do this manually, i.e. make a regular class with all the properties for every NSManagedObject and then manually copy all those values.
I do know how to read property names and values dynamically from my managed object, but how to set them on a Swift class in a way that I can then use those values like
mySwiftObject.name
which returns a String or
mySwiftObject.age
which returns a Number (as those are the types on the Managed Object). Custom subscripting and stuff like that came to my mind, but I didn't manage to achieve this... Is there a nice way to do exactly that?

Related

Using Swift reflection to initialize a general class object with data. Cant iterate through properties

I have noticed a class where the coding key for a new property was not added to a Codable object. As a result, data was silently getting dropped. I would like for this to never happen again.
The goal is to write a more general unit test for any object that is Codable that does the following.
Create an instance of the object with "random" data
Encode the object
Decode the object and see if it is equal to the original object
Thus far I have tried Mirror(reflecting: ClassName.self). The goal is that I could essentially go through each property and come up for a way to generally initialize it with random data. The problem is its children object is not particularly useful. Its a very weird object that does not even loop like a collection as I have seen in some example code.
Curious then if it is even possible to initialize an object with random data in its fields using Swift reflection. A lot of the example code for doing on this rely on the object being set up properly with Encode/Decode which is an assumption I cannot take.

What are the functional differences between Coredata's CodeGen 'manual/none + create NSManagedObject subclass' vs. 'category/extension'

I've read Subclassing NSManagedObject with swift 3 and Xcode 8 beta and read this great tutorial. Still have questions on some points.
The similarities are:
I can customize both classes however I like.
I can add new attributes or remove or rename attributes. ie for category/extension it will get updated upon a new build (in the derived data), and in case of manual/none it will leave the class file intact and update the extension in the file navigation ie I won't end up with a duplicate file. This is all handled by Xcode because they are marked with a preprocessor #NSManaged
Dumping something like #NSManaged public var name: String? straight into an existing NSManagedObject subclass is not allowed. I tried to do entity.name = "John" but I got the following error: reason: '-[SomeEntity setName:]: unrecognized selector sent to instance 0x60400009b120'. I believe that's reasonable. I think without using the Core Data Model Editor the setter/getter accessor methods are not created.
The differences are:
For Category/Extension you just need to create the class yourself and add any extra functions/properties you need.
For Category/Extension the attributes are created in derived data which is enough. Because you never need to see that file. Its existence is enough to get things working.

And specifically in the context of making changes to your NSManaged properties:
Changing property type, e.g. NSDate to Date is allowed only for Manual/None . Example here
Changing optionality of a type, e.g. String? to String is allowed only for Manual/None. Example here
Changing a property access level, e.g. from public to private is allowed only for Manual/None. Example here
Having that said there is significant difference if I choose Manual/None codegen and but don't select 'create NSManagedObject subclass'. In that case I have start writing all the code myself (subclass from NSManagedObject and write NSManaged for every property)...or if I don't write all that code myself then I can still access/set fields using KVC which is awkward!
In a nutshell I'm just trying to figure out the full extent of capabilities that I can get from using Manual/None.
Question: Aside from the 9 notes which I need to know if I have validated correctly, an important question would be: how does me changing NSDate to Date or optional to non-optional not break the mappings between my NSManagedObject class and my object graph all while changing an NSDate property to String does break!! Does this have something to do with things that have guaranteed casting between Swift and Objective-C ie things that can be casted through as — without ? or !?
To address each of your notes and considering the cases where codegen is set to Manual/None and Category/Extension:
Yes, in either case you can customise the classes however you like (within limits - for example, the class must be a subclass - directly or indirectly - of NSManagedObject).
Correct. You can add, amend or delete attributes in the model editor. In the Category/Extension case, the relevant changes will be made automatically. In the Manual/None case, you can either manually update the Extension (or the class file) or you can redo the "create NSManagedObject subclass" which will update the Extension with the amended attribute details. If you do not do this, Xcode will not recognise the new attribute details and will not provide code completion for them (nor will it successfully compile if you try to override code completion). But unlike what you think this has nothing to do with the properties being marked as #NSManaged.
Correct. Adding an #NSManaged property to the class definition (or Extension) is enough to tell Xcode that the property exists (so you can reference them in code) but does not create the corresponding getter/setter. So your code will crash.
Yes, for Category/Extension just create and tailor the class file as you require.
Yes, for Category/Extension the properties are declared in the automatically created Extension file in Derived Data.
Changing the property definition in any way - from Date to NSDate, or marking it private, or whatever - can only be done in the Manual/None case because the Extension file in Derived Data is overwritten with each new build so any changes are lost.
Ditto
Ditto
Correct. You could write your app without ever creating separate NSManagedObject subclasses (automatically or manually), if you use KVC to access the properties.
As to your final point: you cannot arbitrarily change the type of the property definition: the type specified in the model editor must correspond to the type specified in the property definition. You can switch between optional and non-optional versions of the same type, and you can switch between Date and NSDate etc, but switching from Date to String will not work. I suspect you are correct that this is due to the bridging between Swift value type and the corresponding Objective-C reference type using as. See here.

Intersystems caché - programmatically create new class

Is it possible to write ObjectScript method, which will create new class in namespace and compile it? I mean programmatically create new class and store it. If so, can I edit this class using ObjectScript later(and recompile)?
Reason: I have class structure defined in string variable and I need to add new class to namespace according this string.
Nothing is impossible. Everything in Caché can be created programmatically. And, Classes is not a execution. There are at least two ways to do it:
simple SQL Query CREATE TABLE, will create a class.
and as you already mentioned ObjectScript Code, which can do this.
All of definition of any classes defined in other classes. Which you can find in package %Dictionary.
The class itself defined in %Dictionary.ClassDefinition. Which have some properties, for defining any parts of classes. So, this is a simple code which create some class, with one property.
set clsDef=##class(%Dictionary.ClassDefinition).%New()
set clsDef.Name="package.classname"
set clsDef.Super="%Persistent"
set propDef=##class(%Dictionary.PropertyDefinition).%New()
set propDef.Name="SomeProperty"
set propDef.Type="%String"
do clsDef.Properties.Insert(propDef)
do clsDef.%Save()
And in latest versions, there is one more way for create/change class. If you have text of class as you can see it in Studio. Then, you can load it in Caché, with class %Compiler.UDL.TextServices
Yes, it is. You likely want to make use of %Dictionary.ClassDefinition and the related %Dictionary.*Definition classes (especially %Dictionary.PropertyDefinition, %Dictionary.MethodDefinition and %Dictionary.IndexDefinition) to create and/or modify your class. Provided your string contains some reasonable representation of the data, you should be able to create the class this way.
The actual class documentation is available at http://docs.intersystems.com/cache20141/csp/documatic/%25CSP.Documatic.cls?CLASSNAME=%25Dictionary.ClassDefinition
You can then compile the class by calling $system.OBJ.Compile("YourPackage.YourClass","ck").
(Note: If your string contains the exported XML definition of the class, you could also write the XML representation to a stream and then call $system.OBJ.LoadStream() to import the XML definition. I would only recommend this if you have an exported class definition to start with.)

size in Memory of handle object property

Hi I'm using an object with superclass dynamicprops & matlab.mixin.Copyable
Now I would like to now the size (memorywise) of each of the dynamic properties. I tried stuff like whos and getfield. But it seems like I have a very hard time to find something.
I know that people strugle to get exactly this for handle objects, which this is one of them... But I was wondering if somebody has a solution on that nevertheless.
yours
magu_
You could use the properties function to get a list of properties exposed by an object. The same can be done using metaclass. You can then iterate over them and use x.(p) syntax to access each by name (dynamic field names). You would determine the size memory-wise the same way you do any other variable (size/class or whos).
Note that you might need to recursively traverse the properties in case they are containers themselves (objects, structs, cell-arrays).

Using enums with Core Data

What is the best way to handle a helper table (I think there's a more technical word for that but it's escaping me at the moment)? For instance, my object named Entity has an entity_type property. That entity_type needs a string description along with it. Let's assume there are only a handful of entity_types possible.
So I can see going a few ways:
Having another Core Data entity object name Entity_Type and joining it to-many so that I can obtain the description easily. This will allow me to use in a UIPickerView easily, for example.
I could also see why #1 is a trap because later on I will need to do something like a switch/case to handle specific functionality for each type. Being a Core Data object, I have no "id" per say in order to do the switch statement. The alternative would be to hard code an enum, but then how would I handle the descriptions?
Maybe a combination of the two?
Any advice or experience with a similar situation would greatly help. I tried searching, but all I turned up was how to find the ID of a CD object, which is irrelevant.
The 'combination' approach you speak of would work something like this:
You have your Entity_Type with a string description, and an NSNumber 'enumValue' attribute.
Then you define an enum type with explicit values for forwards and backwards compatibility (you don't want people inserting a new enum at the top and breaking everything).
// these values must not change
enum Foo {
FooType1 = 1,
FooType2 = 2
};
Now, you don't want to deal with your 'enumValue' attribute as an NSNumber, so rather than using #dynamic to generate the property, you define your own getter/setter to expose a native enum value rather than an NSNumber. Something like this:
- (void)setEnumValue:(enum Foo)newValue
{
NSNumber *numberValue = [NSNumber numberWithInt:newValue];
[self willChangeValueForKey:#"enumValue"];
[self setPrimitiveValue:numberValue forKey:#"enumValue"];
[self didChangeValueForKey:#"enumValue"];
}
- (enum Foo)enumValue
{
[self willAccessValueForKey:#"enumValue"];
NSNumber *numberValue = [self primitiveValueForKey:#"enumValue"];
[self didAccessValueForKey:#"enumValue"];
// optionally validate against possible enum values, maybe handle the case
// when you are reading a database made by a later version which has new
// unknown-to-us values, etc.
return (enum Foo) [numberValue intValue]
}
I have written this code from memory but that's the general gist of things. The getter/setters talk to the underlying managed object's NSNumber value, but your object itself exposes the property as your strongly typed enum type.
You can then define some helper methods to fetch out the associated entity for an enum value. This should just be a simple fetch request with a enumValue == %# predicate.
You also have to be careful with dealing with unknown enum values. An older version of your software may end up reading a database that contains new enum values that it has no knowledge of.
I've used enums in the past. Like I have a entity to represent a cost and it has a costType which I define as an enum and store in core data as an int. There are 4 possible costTypes (fixed, time, product, travel) and depending on the cost type the cost value will be calculated differently.
I think this is what your getting at, else I'd say give me a firmer example.
I'd suggest two more tools to aid.
Be aware of the NSObject "description" method which you can override, to provide string representation of anything. So if you subclass NSNumber to create an NSNumber that only allows your enumerated set of values, you can also add the "description" method that will simply lookup the value as index in some array of descriptions. Something like
Be very aware of NSValueTransformer! you can create a standalone transformer from any type to any type (and back, for two-way transformers). You can attach a transformer directly to the UI in your .xib, so when you set a NUMERICAL value (your enum) to the UI field, the user will see THE TRANSFORMED (string) value. This also works the other way round.
I'm not attaching code because I'm in a hurry, but I'll do sometime soon.
The above methods are alternative solutions, but maybe you can combine them in the manner suggested by Mike Weller --- Add a new strongly-typed enumerated accessor to the attribute in core-data (which will be some kind of int), but instead of using an enum, use a subclass of NSNumber that has "description" overridden, and Enum accessors as well.
Then define a transformer for this class (into string) that will simply return the description when transforming to string, and will do the opposite when given the description.
Attach this transformer to your UI, and voila!
The techniques described here are Mac too, not just iOS.