Core Data managed objects in swift - is nil allowed? - ios

I have created NSManagedObject subclasses for my model objects in swift.
Typically my pattern is to create an instance of an object and then set properties on it, then save.
The new objects have properties that are set to nil. They are not optionals, though. I thought in swift this wasn't allowed?
A lot of times I need to check for values, but if I try something like:
if (managedObject.property == nil) I crash.

It seems Xcode doesn't automatically make managed vars optional when creating NSManagedObject subclasses. If the values are set as optional in the model, they should be optional in the subclass as well. (I set them as optional manually)
class ClassWithOptionalName: NSManagedObject {
#NSManaged var name: String?
}

Is managedObject.property optional value?
class CustomManagedObject: NSManagedObject {
#NSManaged var aProperty: String?
^
}

Related

How to set a NSManaged variable?

I have a NSManagedObject class with two relationships: courseAand courseB.
These relationships should be represented in a dynamic variable. How is it possible to change this variable from outside the class?
#objc(Universtity)
public class Universtity: NSManagedObject {
dynamic var name: String {
get {
let name = self.courseA?.name
return name!
}
}
}
For example from within a ViewController like University.name = University.courseB.name ?
I was thinking about a Notifikation, but this seems maybe a little more complicated as it could be.
And if there is no other way, how should I implement the observer inside the University class?
Thank you for every idea.
Looking at your code, you have declared a "computed" or "ready-only" variable. This is a variable whose value comes from another variable or combination of variables.
I can't see your data model, so it's not clear if you have defined a name parameter in the Core Data model. Regardless, if you have the logic is somewhat confused, because the getter you have defined means any value it may hold would be ignored anyway. You would need to define a setter to set self.courseA.name if you want to ensure the value can be written to. You don't need to worry about key-value coding notifications, because they will be triggered by the Core Data Managed Object.
public class Universtity: NSManagedObject {
dynamic var name: String {
get {
let name = self.courseA?.name
return name!
}
set(newValue) {
courseA!.name = newValue
}
}
}
Also the pattern you have used to force unwrap a non-optional value in your getter isn't optimal. I haven't edited this because that is another discussion, but I would suggest asking yourself the question am I sure why I am doing this? for every "?" and "!" you use.

Core Data deleteObject sets all properties to nil

I'm a little new CoreData.
When I call deleteObject() with object on my NSManagedContext object, its setting all the properties in the object to nil. Is there anyway for me to avoid this? I don't want to be nullified.
My project is in Swift.
You're misunderstanding the purpose of CoreData. It's a way of managing a persistent store, which means that whatever you tell your context, is absolute. So if you deleteObject(), that object gets prepared for deletion and you're not supposed to touch it anymore.
Instead, you want some kind of mirror object, that allows you to create a new copy of the NSManagedObject for in-memory use. You could do it like this;
struct MirrorManaged {
var text: NSString
}
class Managed: NSManagedObject {
#NSManaged var text: NSString
func copyToMemory() -> MirrorManaged {
return MirrorManaged(text: self.text)
}
}

Swift - casting a nil core data string as an optional value

I have a field stored on a core data object called "metadata" which is of type String (no optional, because Apple docs say not to mess with optionals in CD). Sometimes, the metadata field is nil. In checking whether this value is nil, I do the following check:
if object.metadata as String? != nil {
...
}
However, my code continuously crashes on this line as an EXC_BAD_ACCESS. I have also tried:
if let metadata = object.metadata as String? {
...
}
Which doesn't work either. I cast objects successfully to optionals in other parts of my code, so I don't understand why this particular case isn't working. How do you check whether a core data property is a nil string?
It looks like what you really want is this:
if object.metadata != nil {
...
}
or this:
if let metadata = object.metadata as? String {
// You can now freely access metadata as a non-optional
...
}
--EDIT--
My mistake, I didn't read the first part of your question thoroughly enough. It looks like the duplicate answer has a solution for this. Essentially, the generated managed object subclass is a bug and you should modify the properties to be either optional or implicitly unwrapped. You can check both of those using the first method for implicitly unwrapped and second for optionals.
There are several questions which discuss the issue of the generated subclasses not producing optional properties. I wouldn't be too concerned about editing the subclasses; there's nothing special about them except that Apple is making it easier to create them.
Check if property is set in Core Data?
Swift + CoreData: Cannot Automatically Set Optional Attribute On Generated NSManagedObject Subclass
--Edit2--
If you really don't want to touch the subclass you can access the property using valueForKey() and could add that as an extension if you wanted something a bit cleaner.
if let metadata = object.valueForKey("metadata") as String? {
...
}
In an extension:
extension ObjectClass {
var realMetadata: String? {
set {
self.setValue(newValue, forKey: "metadata")
}
get {
return self.valueForKey("metadata") as String?
}
}
}

Swift + CoreData: Can not set a Bool on NSManagedObject subclass - Bug?

I have a little strange issue which I can't seem to figure out, I have a simple entity with a custom NSManagedObject subclass:
#objc(EntityTest) class EntityTest: NSManagedObject {
#NSManaged var crDate: NSDate
#NSManaged var name: String
#NSManaged var completed: Bool
#NSManaged var completedOn: NSDate
}
This is the problem, I can create the object fine and set the all the values and store in in an array. However late on, when I try to retrieve the same object, I can set all the values EXCEPT the "completed" field. I get a run-time error saying "EXC_BAD_ACCESS", I can read the value, just can not set it.
The debugger points to:
0x32d40ae: je 0x32d4110 ; objc_msgSend + 108
0x32d40b0: movl (%eax), %edx
Maybe some issues due to it being treated as an Objective-C class and trying to send a message to set boolean which I know is a bit funny with CoreData originally representing them as NSNumbers.
Any ideas? I created the class myself, it is not generated.
EDIT:
entity.crDate = NSDate() // succeeds
entity.completed = false // fails
entity.completed.setValue(false, forKey: "completed") //succeeds
So for setting the bool, using the setValue of NSManagedObject works but not the direct setters, though for the non-bool properties, I can set it using the setters.
UPDATE:
While checking this a bit more, it seems like the first time I set the value after getting from NSEntityDescription, it uses normal Swift accessor methods. Later on when I try to access the same object (which was stored in an array) it attempts to treat it as a Objective-C style object and sends a message for method named "setCompleted". I guess it makes sense since I use the dot notation to access it and I used the #objc directive.
I tested this by creating a "setCompleted" method, however in the method I set the value using "completed = newValue" which makes a recursive call back to "setCompleted" causing it to crash... Strange, so at this moment still can't don't have a proper fix. It seems to only happen with Bools.
Only workaround is use the "setValueForKey" method of NSManagedObject. Perhaps file this as a bug report?
If you let Xcode 6 Beta 3 create the Swift files for your entities, it will create NSNumber properties for CoreDatas Boolean type.
You can however just use Bool as a Swift type instead of NSNumber, that worked for me without using the dot syntax though.
It will set the Swift Bool with a NSNumber, that maybe leads to a bug in the dot syntax.
To make it explicit you should use the type NSNumber for attributes in the entity with the Boolean type. Then create a computed property (in iBook The Swift programming language under Language Guide -> Properties -> Computed Properties) to return you a Swift Bool. So would never really store a Bool.
Like so:
#NSManaged var snack: NSNumber
var isSnack: Bool {
get {
return Bool(snack)
}
set {
snack = NSNumber(bool: newValue)
}
}
Of course it would be cool to hide the other (NSNumber attribute), but be patient and Apple will implement private attributes in the future.
Edit:
If you check the box create skalar types it will even use the type Bool in the automatically created Swift file!
So I think it is a bug.
Following on from CH Buckingham who is entirely correct. You are attempting to store a primitive type in core data where it is expecting an NSNumber.
The correct usage would be entity.completed = NSNumber.numberWithBool(false)
This is also why you cannot retrieve this completed value as a bool directly and thus you would need to write:
var: Bool? = entity.completed.boolValue()
You can downcast your property from NSNumber to Bool type like this:
var someBoolVariable = numberValue as Bool
It works for me in this way:
self.twoFactorAuthEnabledSwitch.enabled = userProfile?.twoFactorEnabled as Bool
In XCode 8 Just set :
user.isLoggedIn = true
its works like a charm
I haven't touched swift, but in Objective-C, a BOOL is not an object, and cannot be an object. It's a primitive, and it looks like you are attempting to tell an Objective-C class to treat a BOOL like an object. But I could be making that up, as I'm not familiar with what #NSManaged does under the hood.
https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/FoundationTypesandCollections/FoundationTypesandCollections.html
I found that it works fine if you specify the class name and module in the data model (instead of leaving the default NSManagedObject).
Once I set that, I can use Bool, Int16, Int32, etc., without any problems.

Setting an NSManagedObject relationship in Swift

How does one add an object to a relationship property in an NSManagedObject subclass in Swift?
In Objective-C, when you generate an NSManagedObject subclass in Xcode from the data model, there's an automatically generated class extension which contains declarations like:
#interface MyManagedObject (CoreDataGeneratedAccessors)
- (void)addMySubObject: (MyRelationshipObject *)value;
- (void)addMySubObjects: (NSSet *)values;
#end
However Xcode currently lacks this class generation capability for Swift classes.
If I try and call equivalent methods directly on the Swift object:
myObject.addSubObject(subObject)
...I get a compiler error on the method call, because these generated accessors are not visible.
I've declared the relationship property as #NSManaged, as described in the documentation.
Or do I have to revert to Objective-C objects for data models with relationships?
As of Xcode 7 and Swift 2.0 (see release note #17583057), you are able to just add the following definitions to the generated extension file:
extension PersonModel {
// This is what got generated by core data
#NSManaged var name: String?
#NSManaged var hairColor: NSNumber?
#NSManaged var parents: NSSet?
// This is what I manually added
#NSManaged func addParentsObject(value: ParentModel)
#NSManaged func removeParentsObject(value: ParentModel)
#NSManaged func addParents(value: Set<ParentModel>)
#NSManaged func removeParents(value: Set<ParentModel>)
}
This works because
The NSManaged attribute can be used with methods as well as
properties, for access to Core Data’s automatically generated
Key-Value-Coding-compliant to-many accessors.
Adding this definition will allow you to add items to your collections. Not sure why these aren't just generated automatically...
Yeah that's not going to work anymore, Swift cannot generate accessors at runtime in this way, it would break the type system.
What you have to do is use the key paths:
var manyRelation = myObject.valueForKeyPath("subObjects") as NSMutableSet
manyRelation.addObject(subObject)
/* (Not tested) */
Core Data in Objective C automatically creates setter methods (1):
By default, Core Data dynamically creates efficient public and primitive get and set accessor methods for modeled properties (attributes and relationships) of managed object classes. This includes the key-value coding mutable proxy methods such as addObject: and removes:, as detailed in the documentation for mutableSetValueForKey:—managed objects are effectively mutable proxies for all their to-many relationships.
As things currently stand with Swift in Xcode6-Beta2, you'd have to implement those accessors yourself. For example if you have an unordered to-many relationship, from Way to Node, you'd implement addNodesObject like this:
class Way : NSManagedObject {
#NSManaged var nodes : NSSet
func addNodesObject(value: Node) {
self.mutableSetValueForKey("nodes").addObject(value)
}
}
Key here is that you'd have to use mutableSetValueForKey / mutableOrderedSetValueForKey / mutableArrayValueForKey. On these sets / arrays, you can call addObject and they'll be stored on the next flush.
You can just use a typed Set instead which is far easier. Following the example provided by #Nycen and #lehn0058 in the previous answer, you can just write:
extension PersonModel {
#NSManaged var parents: Set<ParentModel>?
}
And then use the insert and remove methods of the Set.
Expanding on the solution above one to many relationships are NSMutableSet so this allows you to directly add or remove the Person NSManagedObject to the Roles in this case a Person has one Role and Roles have many Person(s)
I have tested this solution under Xcode Beta-3 and this works!
This code takes out the Department to simplify showing the one to one and one to many code required to access Roles from a Person and Persons from a Role.
import CoreData
#objc(Person) class Person: NSManagedObject {
#NSManaged var name: String
//One to One relationship in your Model
#NSManaged var roles: Roles
}
#objc(Roles) class Roles: NSManagedObject {
#NSManaged var role: String
//One to Many relationship in your Model
#NSManaged var persons: NSMutableSet
}
extension Roles {
func addPersonsObject(value: Person) {
self.persons.addObject(value)
}
func removePersonsObject(value: Person) {
self.persons.removeObject(value)
}
func addPersons(values: [Person]) {
self.persons.addObjectsFromArray(values)
}
func removePersons(values: [Person]) {
for person in values as [Person] {
self.removePersonsObject(person)
}
}
}
As of Xcode 8 and Swift 3.0, Xcode now generates accessors for relationships. For example, I have an NSManagedObject class Store, that has a one to many relationship with Items; I've called that relationship SellsItems. The generated class for Store now has the following extension to add and remove from SellsItems. Adding or removing items to the relationship is as simple as calling these functions.
// MARK: Generated accessors for sellsItems
extension Store {
#objc(addSellsItemsObject:)
#NSManaged public func addToSellsItems(_ value: Item)
#objc(removeSellsItemsObject:)
#NSManaged public func removeFromSellsItems(_ value: Item)
#objc(addSellsItems:)
#NSManaged public func addToSellsItems(_ values: NSSet)
#objc(removeSellsItems:)
#NSManaged public func removeFromSellsItems(_ values: NSSet)
}
As you only need to set one side of a relationship for both to be set nowadays, it's particularly simple if you have a 1<->many relationship, e.g. a Department object has multiple Person objects, then you can just use:
aPerson.department = aDepartment
If you check you'll find that aDepartment.people (assuming that is the reciprocal relationship you've set up) will now contain the 'aPerson' Person object.
If the relationship is many<->many then one of the more complex solutions above appears necessary.
Let's say you have the following entities:
Person
Role
Department
In your Person entity, they have a to-many relationship with Role and to-one with Department. Your managed object might look something like this:
class Person : NSManagedObject
{
#NSManaged var roles : Array<Role>
#NSManaged var department : Department
}
Relationships with inverses (all should have them) only require one side to be set for the link to be established.
For example, if you set a Person's department property to a Department object, the inverse Department.people property would now also have this Person object contained inside.

Resources