12

I've been trying to get my head around adding objects in relationships using CoreData and Swift. I am at a loss, I do not understand why my code does not work. I am trying to add an "Event" to a "Team". I can not find the difference between accepted answers (that should work), and my code (that does not).

Teams.swift:

import Foundation
import CoreData

class Teams: NSManagedObject {

    @NSManaged var teamName: String
    @NSManaged var matches: NSSet

}

extension Teams {

    func addEventToTeam(event:Event) {
        //self.mutableSetValueForKeyPath("matches").addObject(event)

        var matchez: NSMutableSet

        matchez = self.mutableSetValueForKey("matches")
        matchez.addObject(event)


        //var manyRelation = self.valueForKeyPath("matches") as NSMutableSet
        //manyRelation.addObject(event)
    }

    func getTeamName() -> String {
        return teamName
    }


}

Calling code (from configure view):

import UIKit
import CoreData

class DetailViewController: UIViewController, NSFetchedResultsControllerDelegate {

    var managedObjectContext: NSManagedObjectContext? = nil

    @IBOutlet weak var detailDescriptionLabel: UILabel!


    var detailItem: AnyObject? {
        didSet {
            // Update the view.
            self.configureView()
        }
    }

    func configureView() {
        // Update the user interface for the detail item.
        if let detail: Event = (self.detailItem as? Event) {
        //if let detail: AnyObject = self.detailItem {

            if let label = self.detailDescriptionLabel {
                label.text = detail.valueForKey("timeStamp").description

                self.insertNewObject(self);
                label.text = String(detail.getNumberOfTeams())

                //detail.getTeams().
                var hej: Array<Teams>
                hej = detail.getTeams()

                label.text = "tjosan"

                for tmpTeam : Teams in hej {
                    label.text = label.text + ", " + tmpTeam.getTeamName()
                }


            }
        }

        if true {


        }


    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.configureView()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


    var fetchedResultsController: NSFetchedResultsController {
        if _fetchedResultsController != nil {
            return _fetchedResultsController!
            }

            let fetchRequest = NSFetchRequest()
            // Edit the entity name as appropriate.
            let team = NSEntityDescription.entityForName("Teams", inManagedObjectContext: self.managedObjectContext)
            fetchRequest.entity = team

            // Set the batch size to a suitable number.
            fetchRequest.fetchBatchSize = 20

            // Edit the sort key as appropriate.
            let sortDescriptor = NSSortDescriptor(key: "teamName", ascending: false)
            let sortDescriptors = [sortDescriptor]

            fetchRequest.sortDescriptors = [sortDescriptor]

            // Edit the section name key path and cache name if appropriate.
            // nil for section name key path means "no sections".
            let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: nil, cacheName: "Master")
            aFetchedResultsController.delegate = self
            _fetchedResultsController = aFetchedResultsController

            var error: NSError? = nil
            if !_fetchedResultsController!.performFetch(&error) {
                // Replace this implementation with code to handle the error appropriately.
                // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                //println("Unresolved error \(error), \(error.userInfo)")
                abort()
            }

            return _fetchedResultsController!
    }    
    var _fetchedResultsController: NSFetchedResultsController? = nil



    func insertNewObject(sender: AnyObject) {
        let context = self.fetchedResultsController.managedObjectContext
        let team = self.fetchedResultsController.fetchRequest.entity
        let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(team.name, inManagedObjectContext: context) as Teams

        // If appropriate, configure the new managed object.
        // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
        newManagedObject.setValue("Lagur Namnurk", forKey: "teamName")

        newManagedObject.addEventToTeam(self.detailItem as Event)


        // Save the context.
        var error: NSError? = nil
        if !context.save(&error) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            //println("Unresolved error \(error), \(error.userInfo)")
            abort()
        }
    }



}

Error message:

2014-08-13 18:38:46.651 Score Calculator 2[10538:829319] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSSet intersectsSet:]: set argument is not an NSSet'
*** First throw call stack:
(
    0   CoreFoundation                      0x00000001028a53e5 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x00000001043b8967 objc_exception_throw + 45
    2   CoreFoundation                      0x000000010280fc6c -[NSSet intersectsSet:] + 940
    3   Foundation                          0x0000000102d0c4a6 NSKeyValueWillChangeBySetMutation + 156
    4   Foundation                          0x0000000102c804fa NSKeyValueWillChange + 386
    5   Foundation                          0x0000000102d0c3fb -[NSObject(NSKeyValueObserverNotification) willChangeValueForKey:withSetMutation:usingObjects:] + 310
    6   CoreData                            0x00000001024178d7 -[NSManagedObject(_NSInternalMethods) _includeObject:intoPropertyWithKey:andIndex:] + 551
    7   CoreData                            0x0000000102418294 -[NSManagedObject(_NSInternalMethods) _maintainInverseRelationship:forProperty:forChange:onSet:] + 276
    8   CoreData                            0x0000000102416ef2 -[NSManagedObject(_NSInternalMethods) _didChangeValue:forRelationship:named:withInverse:] + 562
    9   Foundation                          0x0000000102c835d6 NSKeyValueNotifyObserver + 356
    10  Foundation                          0x0000000102c827fd NSKeyValueDidChange + 466
    11  Foundation                          0x0000000102d0c7ee -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:withSetMutation:usingObjects:] + 118
    12  CoreData                            0x00000001024180b0 -[NSManagedObject didChangeValueForKey:withSetMutation:usingObjects:] + 80
    13  CoreData                            0x000000010242fa11 -[_NSNotifyingWrapperMutableSet addObject:] + 161

edit: a couple of clarifications. Teams and Event have a multi-to-multi, unordered relationship.

Martin Lindskog
  • 283
  • 1
  • 2
  • 10
  • it is apple's bug:[a good explain in stackoverflow](http://stackoverflow.com/questions/7385439/exception-thrown-in-nsorderedset-generated-accessors) – childrenOurFuture Nov 04 '15 at 11:44

2 Answers2

12

Yesss!! I found the answer!

I had created a new function in the class Events.swift (the other side of the relationship).

I had written the following code:

import Foundation
import CoreData

class Event: NSManagedObject {

    @NSManaged var timeStamp: NSDate
    @NSManaged var teams: NSSet

}

extension Event {

    func addTeamToEvent(team:Teams) {
        var teamz = self.mutableSetValueForKey("teams")
        teamz.addObject(team)
    }

    func getNumberOfTeams() -> Int {
        return self.teams.count;
    }

    func getTeams() -> [Teams] {
        var tmpsak: [Teams]
        tmpsak = self.teams.allObjects as [Teams]
        tmpsak = self.teams.allObjects as [Teams]

        return tmpsak
    }

}

which I thought was unrelated. However, renaming getTeams to getTeamsAsArray removed the problem. I am guessing that CoreData, when filling in the other end of the relationship, uses a built-in function called getTeams() (since the other class was called Teams). I accidentally overrode(?) it, causing it to fail.

Thank you for your suggestions, and I hope this can be helpful to someone else!

On a somewhat related note, a bug with similar symptoms was identified a few years ago (and appears to still be present), that shows itself in auto-generated code when using ordered many-to-many relationships.

Martin Lindskog
  • 283
  • 1
  • 2
  • 10
  • If you look at the code hints Apple provides you should be adding your custom methods to the subclass not the extension. Read what they say in the comments there. In the extension they write: "// Choose "Create NSManagedObject Subclass…" from the Core Data editor menu // to delete and recreate this implementation file for your updated model." and in the subclass they write: "// Insert code here to add functionality to your managed object subclass". Apple doesn't usually say things in comments without a reason. Your code will be overwritten if you regenerate those subclasses. – smileBot Apr 27 '16 at 01:22
  • Also, just cast your set to an NSMutableSet. Doing it with a key is error prone since the compiler cannot check this string. – smileBot Apr 27 '16 at 01:24
  • This actually worked for me too! I had a function `getChoices` where choices is a ToMany relationship and thus an `@NSManaged var choices: NSSet?` variable. When I changed it from `getChoices` to `getChoicesAsArray` the problem went away. – Coder1224 Sep 20 '16 at 05:28
  • ARGH Thanks!!! This one set me back a LONG way. It was driving me crazy. – LateNate May 29 '17 at 20:55
  • I just had this same problem. I had a method called `getFields()` and also have a relationship called `fields` and they obviously collided. That really sucks though because I need to use getFields to support a JavaScriptCore API that I have in my app. But I need it to return an array and not a Set – Tap Forms Aug 15 '20 at 22:21
1

If you use the objective C generated NSManagedObject subclasses, they include custom methods for adding a single object - I think these are tried and tested and seem better than what is currently there for swift.

Theoretically you would just need to just set the new set with whatever new set you created. One option would be:

    var matchobjs = matches.allObjects as [Event]
    matchobjs.append(event)
    matches = NSSet(array: matchobjs)

However the error looks very similar to this post:

Which to me looks like theres something fishy going on again with many-to-many relationships...

Community
  • 1
  • 1
James Alvarez
  • 6,659
  • 6
  • 26
  • 44
  • Thank you for your reply! The new code does however not work either, I get the same error message as before on the statement "matches = NSSet(array:matchobjs)" – Martin Lindskog Aug 13 '14 at 19:50
  • I would recommend using the objective C generated headers then, they provide methods for you - just add the header names to the bridging header. The error is v strange, and possibly a bug, I tried to create a project with swift generated headers, but I found it kept breaking for even the most simple stuff - ok so this sometimes happens in a beta. The error incidentally looks like which is a long standing bug with many to many relationships, and perhaps the swift generated headers do something weird to cause the same thing to happen... – James Alvarez Aug 13 '14 at 21:02