12
votes

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?

4
NSSet is not available in Swift? where did you find that?Bryan Chen
Also the error message indicates that the relationship is ordered, not unordered.Martin R
@BryanChen ah yes, your comment made me realize that NSSet is available through Foundation.Bouke
@MartinR I've copied the error message where it was still defined as ordered set.Bouke
@bouke: The error message still mentions ".. value for ordered to-many relationship". Could you please copy/paste the actual error message into the question, to avoid confusion of future readers?Martin R

4 Answers

17
votes

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

12
votes

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)
11
votes

Actually you can just define:

@NSManaged var employees: Set<Employee>

And use the insert and remove methods of the Set directly.

0
votes

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")
    }
}