13

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?

Bouke
  • 11,768
  • 7
  • 68
  • 102
  • NSSet is not available in Swift? where did you find that? – Bryan Chen Jun 22 '14 at 10:29
  • Also the error message indicates that the relationship is *ordered*, not unordered. – Martin R Jun 22 '14 at 10:58
  • @BryanChen ah yes, your comment made me realize that NSSet is available through `Foundation`. – Bouke Jun 22 '14 at 10:59
  • @MartinR I've copied the error message where it was still defined as ordered set. – Bouke Jun 22 '14 at 11:00
  • @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 Jun 22 '14 at 11:36
  • @MartinR good idea; updated the post accordingly. – Bouke Jun 22 '14 at 12:08
  • As boule points out below, relationships in CoreData are sets, not arrays. – David Berry Jun 22 '14 at 16:50
  • For others that find this question because they want to learn how to use relations in Core Data, i finally found this tutorial: [Core Data and Swift: Relationships and More Fetching](http://code.tutsplus.com/tutorials/core-data-and-swift-relationships-and-more-fetching--cms-25070) – Jakob Hviid PhD Nov 23 '15 at 13:03

4 Answers4

18

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

GoZoner
  • 67,920
  • 20
  • 95
  • 145
Keenle
  • 12,010
  • 3
  • 37
  • 46
  • Hey Keenie, When I use your extension I get the error: `"'NSSet' does not have a member named 'addTagObject'"`. Any idea what I might be doing wrong? – Pirijan Aug 04 '14 at 16:34
  • 1
    @Pirijan, you must be invoking addTagObject method on tags: NSSet property while you should invoke it on the object that has tags property. – Keenle Aug 04 '14 at 17:36
  • 1
    Thanks Keenle, I couldn't figure it out so I posted a more thorough question here: http://stackoverflow.com/questions/25127090/saving-coredata-to-many-relationships-in-swift – Pirijan Aug 04 '14 at 20:36
  • Thanks, although later posted, your answer is more complete and I reward you for that :). – Bouke Nov 01 '14 at 10:59
  • This did not work for me. After calling the remove function on the parent of the to-many relationship, the attempt to save failed because the child still existed but its required relationship to its parent was nil. – Mike Taverne Jan 20 '15 at 21:12
12

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)
Arnaud
  • 17,268
  • 9
  • 65
  • 83
11

Actually you can just define:

@NSManaged var employees: Set<Employee>

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

FranMowinckel
  • 4,233
  • 1
  • 30
  • 26
  • Have you checked this actually work? If you save the model after having modified the employees set this way, will the modifications actually be saved? – Frizlab Feb 27 '16 at 10:51
  • 2
    I'm actually using this approach in this very same moment and it's working. – FranMowinckel Feb 27 '16 at 10:53
  • Interesting! I did not know it was possible. Apple should really clarify the Core Data/Swift usage I think… – Frizlab Feb 27 '16 at 12:32
  • Are there common cases where the set should be an Optional? Or is it best to define it as in your example? – blwinters Apr 12 '16 at 14:37
  • I use it just for *Non Ordered*, *Non Optional*, *To Many*, with *Cascade* delete rule, relationships, which happen to be the most common cases (at least in my code). If you mark your model relationship as *Optional* then it should be `Set?` – FranMowinckel Apr 12 '16 at 14:46
  • 1
    That's very helpful, thank you. I find that a lot of Core Data information is couched in Objective-C conventions, so Swift-specific explanations are much appreciated. – blwinters Apr 12 '16 at 14:52
  • This is probably only possible in iOS 10 because of the improvements to Core Data, but it is much much better than the other, older options – Yi Jiang Jan 30 '17 at 02:29
0

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")
    }
}
yo.ian.g
  • 1,354
  • 2
  • 14
  • 21