I am trying to store an array of Ints in Core Data. I have configured my model with:
Entity: Performance
Attribute: secondsSinceStart: Transformable
Within the Xcode Data Model Inspector panel I have set the attribute's custom class as [Int]
Since setting the custom class, Xcode produces the following warning:
file:///....xcdatamodel: warning: Misconfigured Property:
Performance.secondsSinceStart is using a nil or insecure value transformer.
Please switch to NSSecureUnarchiveFromDataTransformerName or a custom NSValueTransformer subclass of NSSecureUnarchiveFromDataTransformer
What do I need to do to satisfy this warning?
I have tried to "Create NSManagedObject Subclass" for this entity, but Xcode then gives an error that Multiple commands produce ....'Performance+CoreDataProperties.o'
Related
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.
How does one save and retrieve a generic Measurement in Core Data?
What I'm looking to do is save either a Measurement<UnitMass> or a Measurement<UnitVolume>.
As can be seen in the image below CoreData is set to accept a Generic Measurement<Unit>
However I'm getting an error when I go to set the value of the measure. Saying that I'm not allowed to do this. I thought the purpose of generics was to support uses like this.
What am i missing?
The error about mismatched types isn't the important part here. The real problem is that transformable attributes only work with classes that conform to NSCoding or for which you've written your own custom value transformer. Since Measurement is not a class and does not conform to NSCoding, you can't use it with a transformable attribute.
Your options are
Don't save the Measurement, save its values, and convert to/from the Measurement when saving/reading property values.
Write your own custom subclass of ValueTransformer that will convert between Measurement and Data.
I'd go with #1. You could add convenience methods on your managed object subclass to handle the conversion.
Update: Using your Measurement<UnitMass> case, I'd do something like:
Give the attribute a Double property named massValue.
Give the attribute a transformable property named massUnit with custom class UnitMass (see below).
Save values with something like this:
let servingMeasure = Measurement<UnitMass>(value:500, unit:.grams)
myObject.massValue = servingMeasure.value
myObject.massUnit = servingMeasure.unit
Retrieve values with something like:
if let unit = myObject.massUnit {
let value = myObject.massValue
let measurement = Measurement<UnitMass>(value:value, unit:unit)
print("Measurement: \(measurement)")
}
This is how the massUnit property is configured:
In Swift, Measurement does adopt the code able protocol, and therefore it can be saved in Core Data through a transformable attribute.
The error that you got is actually pretty clear. You can't save a specific Measurement type Measurement<UnitMass> to the generic type Measurement<Unit> in Core Data. You can't do it in the main code, either. The fix is simple, for each attribute specify the specific type for that attribute as the Custom Class.
I am transferring data from my iOS app to its Watch extension via the application context. I want to send a custom object I've created (named WeatherReport).
let context = ["report" : WeatherReport]
WCSession.defaultSession().updateApplicationContext(context)
However, I get the following error:
Value of type WeatherReport does not conform toe expected dictionary
value type 'AnyObject'
I am wondering why I am unable to set my custom object as a value in the dictionary I am trying to pass as the applicationContext.
Even if you could get past the compiler error you would get a runtime error. WCSession dictionaries can only contain property list types, which are just basic types such as strings, numbers, data, etc.
If you really want to send your custom object you'll have to serialize it first. The better solution is likely to convert your object in to a plist dictionary (each property becomes a key-value in the dictionary).
I'm making a purely Swift project, and when I create an entity in model file, then use Editor->Create NSManagedObject Subclass to create class file for the entity, in the model, the Class property for entity becomes PRODUCT_MODULE_NAME.entityName, this will cause core data to fail loading NSManagedObject subclass instance.
I know how to get pass by this by using #objc() and rename the class property in model file, but is there any better idea?
Two options:
Replace the PRODUCT_MODULE_NAME with the value of this build setting. By default, it will be the same as your TARGET_NAME. The full value in the Class field should be something like MyApp.entityName.
Use only entityName in the Class field and prefix your swift class with #objc(entityName)
The representedClassName field in the data model appears to be evaluated at runtime so it needs a literal value.
Apple is giving the example, that I could make a MyColor class for holding color data, and use this with an NSAttributeDescription object by calling the -setAttributeValueClassName: method.
But what's missing there is this: How's MyColor persistet? And what do I have to provide in -setAttributeType: when I do that? There's no type like "custom class" or something this way.
Would Core Data just serialize MyColor and store that in an String data type somehow? How does that work?
Edit: Does this have to do anything with value transformers?
If you read that same documentation, you see that they recommend using transformable attributes for custom classes:
Note: The example for an object value
uses an instance of NSColor; if you
are using Mac OS X v10.5, you should
typically use a transformable
attribute instead.
The iPhone OS 3.0 Core Data is similar to Snow Leopard era Core Data, so the above statement applies to it as well.
You will want to create an NSValueTransformer that will convert your custom type to a type that Core Data can handle, like NSData. This CocoaDev wiki page links to some examples of NSValueTransformer. I provide some code that I use for transforming UIImages to PNG data in this answer.