In CoreData, I have defined an unordered to-many relationship from Node
to Tag
. I've created an Swift entity like this:
import CoreData
class Node : NSManagedObject {
@NSManaged var tags : Array<Tag>
}
Now I want to add a Tag
to an instance of Node
, like this:
var node = NSEntityDescription.insertNewObjectForEntityForName("Node", inManagedObjectContext: managedObjectContext) as Node
node.tags.append(tag)
However, this fails with the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-many relationship: property = "tags"; desired type = NSSet; given type = _TtCSs22ContiguousArrayStorage000000000B3440D4; value = (
"<_TtC8MotorNav3Tag: 0xb3437b0> (entity: Tag; id: 0xb343800 ; data: {...})"
).'
What is the correct type for to-many relationships?
To be able to work with one-to-many relationship in Swift you need to define property as:
class Node: NSManagedObject {
@NSManaged var tags: NSSet
}
If you try to use NSMutableSet
changes will not be saved in CoreData. And of course it is recommended to define reverse link in Node
:
class Tag: NSManagedObject {
@NSManaged var node: Node
}
But still Swift cannot generate dynamic accessors in runtime, so we need to define them manually. It is very convenient to define them in class extension
and put in Entity+CoreData.swift
file. Bellow is content of Node+CoreData.swift
file:
extension Node {
func addTagObject(value:Tag) {
var items = self.mutableSetValueForKey("tags");
items.addObject(value)
}
func removeTagObject(value:Tag) {
var items = self.mutableSetValueForKey("tags");
items.removeObject(value)
}
}
Usage:
// somewhere before created/fetched node and tag entities
node.addTagObject(tag)
Important: To make it all work you should verify that class names of entities in you CoreData model includes your module name. E.g. MyProjectName.Node
As of Xcode 7 and Swift 2.0, the release note 17583057 states:
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.
@NSManaged var employees: NSSet
@NSManaged func addEmployeesObject(employee: Employee)
@NSManaged func removeEmployeesObject(employee: Employee)
@NSManaged func addEmployees(employees: NSSet)
@NSManaged func removeEmployees(employees: NSSet)
These can be declared in your NSManagedObject subclass. (17583057)
So you just have to declare the following methods and CoreData will take care of the rest:
@NSManaged func addTagsObject(tag: Tag)
@NSManaged func removeTagsObject(tag: Tag)
@NSManaged func addTags(tags: NSSet)
@NSManaged func removeTags(tags: NSSet)
Actually you can just define:
@NSManaged var employees: Set<Employee>
And use the insert
and remove
methods of the Set
directly.
Building on @Keenle's answer, if you want to be cheeky and concise and be able to say
node.tags.append(tag)
one can wrap the call to self.mutableSetValueForKey:
class Node: NSManagedObject {
var tags: NSMutableOrderedSet {
return self.mutableOrderedSetValueForKey("tags")
}
}