I have a core data entity named Film which has properties title and date. I noticed that the generated NSManagedObject subclass contains optional NSManaged properties even though I marked the properties as non optional in the core data inspector.
Can I can manually change it as non-optional property or is it a better choice to leave it as optional? Why?
"Optional" means something different to Core Data than it does to Swift.
If a Core Data attribute is not optional, it must have a non-nil value when you save changes. At other times Core Data doesn't care if the attribute is nil.
If a Swift property is not optional, it must have a non-nil value at all times after initialization is complete.
Making a Core Data attribute non-optional does not imply that it's non-optional in the Swift sense of the term. That's why generated code makes these properties optional-- as far as Core Data is concerned, it's legal to have nil values except when saving changes.
Update: After writing this answer I wrote a deep dive blog post explaining things in more detail: https://www.atomicbird.com/blog/clash-of-the-optionals/
This is a known issue. Some people change it to non-optional with no adverse effects, I keep it the way it was generated and hope for early fix.
It always helps if you submit a bug to Apple to increase visibility and priority.
Create managedobject class and change the entity class type to manual and add these classes to your project scope.
Edit your managedObject to make them non-optional. This means you need to maintain this class yourself and do any changes both in the core data model and the class
If your data model is stable and won't be changed then you can use this.
The Optional checkbox in the data model inspector has nothing to do with Swift optionals. The checkbox determines whether or not the attribute is required to have a value.
If you deselect the Optional checkbox for an attribute, you must give that attribute a value or you will get an error when saving. By selecting the Optional checkbox you can save without giving the attribute a value. Suppose you have a description attribute that's a string. If you select the Optional checkbox you could leave the description blank and still save the entity.
Here's another example. Suppose you have text fields to let a person enter their home, work, and cell phone numbers. These phone numbers should be optional attributes. You wouldn't want to require someone to have a home phone number, a work phone number, and a cell phone number just to save the person's data.
Related
I am relatively new to Swift Programming and recently tried out Core Data for the first time. However, I am having a hard time understanding several (for me) strange behaviours I am encountering:
I have create all my entities and their attributes in the ".xcdatamodeld" file. Codegen is on "Manual/None". Some of the attributes are marked a non-optional. Still, when I generate the NSManagedObject Subclass files, I still see them as optional in the "...Properties" files, i.e. having a "?" after the type. Why is that?
Somewhat relating/in contrast to the first bullets, the attributes for some entities do not have the "?", although they are marked as optional. When I try to add the "?", I get an error "Property cannot be marked #NSManaged because its type cannot be represented in Objective-C". Why is that?
When I'm creating the NSManagedObject Subclasses and select some subfolder in my project for them to be created in, they are put at the top of the hierarchy tree, regardless.
What happens if I change information in the "...Class"/"...Properties" files generated by Core Data which would conflict with what is in the ".xcdatamodeld" file. What takes precedence? How are they related?
In general I find there is not much detailed descriptions available on Core Data except introductory things. Would anyone know some good resources on that? Website? Youtube Videos? Books?
Answers:
Creating the subclasses manually treates the optionals not accurately. Check any attribute and remove the question mark in the class if it's non-optional in the model.
Scalar Swift optional types (Int?, Double?, Bool?) cannot be represented in Objective-C. I recommend to declare them as non-optional.
Never mind, it has no effect where the classes are located, the main thing is that the file name is black (valid) in the Project Navigator and the target membership is assigned correctly.
in the Codegen Manual / None case you are responsible that the types in the model match the types in the classes otherwise you could get unexpected behavior. Any change in the class must be done also in the model and vice versa. However you can replace suggested ObjC classes like NSSet or NSDate with native Swift types Set<MyClass> or Date without changing the type in the model.
I have a core data model that contains mainly optional attributes. I assumed then that I would not need to unwrap the values that I am assigning to these optional attributes. e.g. I thought I would be able to do:
myEntity.gravity = currentOrder.gravity
(myEntity.gravity being optional)
However, Swift still requires me to unwrap currentOrder.gravity. I would have thought given that the variable I am assigning to is optional that I would not need to unwrap this.
Update:
Here is the definition of one of the core data entities I am describing:
<attribute name="percentComplete" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
The entity itself:
<entity name="AircraftMeasurementsCD" representedClassName="AircraftMeasurementsCD" syncable="YES" codeGenerationType="class">
It seems you're equivocating on the word "optional".
The word "optional" in the attribute description optional="YES" is not the same as, and has nothing to do with, the Swift Optional enum type.
The former is merely the ordinary English word "optional", meaning "not required" — for a particular entity, there might or might not be a value for this attribute, which is a Float, but even if there isn't, it's a valid entity. Nothing in the story says that this attribute's type is Optional<Float>. Its type is Float. And you can't assign a Swift Optional where its wrapped type is expected; you have to unwrap it.
Indeed, this point is made very explicitly by the documentation:
Optional attributes aren’t required to have a value when saved to the persistent store. Attributes are optional by default.
Core Data optionals aren’t the same as Swift optionals. [My italics.]
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.
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.
In core data, in the xcdatamodel file, if i want to specify a parent object, but for one of the children of the parent object, i want a property to be optional, and for the other, i don't want to check optional, is there a way to do that? or should i just take the property out of the parent and put it in the children so that I can make one optional and the other not optional?
I think the best way to do is to make the property optional, and changing this behavior in some of the subclasses by implementing your own validation mechanism (documentation available in the Core Data docs, validation is part of the API)
Then by default the property is optional, but in the subclasses you want it to be required you can simply invalidate the property's value when it's nil or empty, and even make it depend on other factors.