How to define CoreData relationship in Swift?

2020-02-10 15:31发布

问题:

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?

回答1:

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



回答2:

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)


回答3:

Actually you can just define:

@NSManaged var employees: Set<Employee>

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



回答4:

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