user3163025
user3163025

Reputation:

Crash with many-to-many NSManagedObject relationship lookup in Swift 2.0

I am trying to implement a many-to-many relationship in Core Data with Swift 2.0 NSManagedObject subclassed, but whenever I try to fetch an object in the relationship I get a strange unrecognized selector sent to class error.

The relationship I am trying to design looks like this:

enter image description here

Each side of the relationship is unordered, and so should be represented within Core Data as an NSSet. Here are what my class implementations look like:

TaggedContent.swift

import Foundation
import CoreData

class TaggedContent: NSManagedObject {
    @NSManaged var tags: NSSet?
}

Tag.swift

import Foundation
import CoreData

class Tag: NSManagedObject {
    @NSManaged var name: String?
    @NSManaged var content: NSSet?
}

When I first launch my app I have no problem populating my data store with data. The problem I have is when I relaunch my app. When I try to look up TaggedContent instances by Tag.name, I continue to run in to some variation of this crash:

Crashed Thread:        0  Dispatch queue: NSManagedObjectContext 0x7fda82f934f0

Exception Type:        EXC_CRASH (SIGABRT)
Exception Codes:       0x0000000000000000, 0x0000000000000000

Application Specific Information:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[CALayer __setObject:forKey:]: unrecognized selector sent to class 0x10426e4a8'
terminating with uncaught exception of type NSException
abort() called
CoreSimulator 179 - Device: iPhone 6 - Runtime: iOS 8.4 (12H141) - DeviceType: iPhone 6

Application Specific Backtrace 1:
0   CoreFoundation                      0x0000000105e95c65 __exceptionPreprocess + 165
1   libobjc.A.dylib                     0x0000000106758bb7 objc_exception_throw + 45
2   CoreFoundation                      0x0000000105e9cfad +[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3   CoreFoundation                      0x0000000105df313c ___forwarding___ + 988
4   CoreFoundation                      0x0000000105df2cd8 _CF_forwarding_prep_0 + 120
5   CoreData                            0x000000010ea7367c _PFCMT_SetValue + 988
6   CoreData                            0x000000010ea71644 -[NSManagedObjectContext(_NSInternalAdditions) _retainedObjectWithID:optionalHandler:withInlineStorage:] + 196
7   CoreData                            0x000000010eab4a9b _PFRetainedObjectIDCore + 331
8   CoreData                            0x000000010eaa737e -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] + 862
9   CoreData                            0x000000010eb1bc1d __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke + 589
10  CoreData                            0x000000010eaa6fd4 internalBlockToNSManagedObjectContextPerform + 84
11  libdispatch.dylib                   0x0000000107024964 _dispatch_client_callout + 8
12  libdispatch.dylib                   0x000000010700dfca _dispatch_barrier_sync_f_invoke + 76
13  CoreData                            0x000000010eaa6eed _perform + 221
14  CoreData                            0x000000010eaa6d18 -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] + 296
15  CoreData                            0x000000010ea602ca -[NSManagedObjectContext executeFetchRequest:error:] + 586

Notice that Core Data is trying to populate data on a CALayer. I've seen several different object types in these crash logs (such as __NSCFString, __NSCFType, UICollectionViewLayoutAttributes, and __NSArrayM to name a few) and I'm baffled as to why. I keep thinking that I must have missed something, but I can't find anything that I missed. If there's anyone out there who has an idea as to why this would happen I would love to know as I'm currently stuck at this point.

UPDATE: I was asked to provide the code that leads to the crash. I have a set of helper methods for simplifying fetch requests, but a this simple (and somewhat contrived) example also causes the same crash:

let fetchRequest = NSFetchRequest(entityName: "Tag")
fetchRequest.predicate = NSPredicate(format: "%K == %@", argumentArray: ["name", "pro"])

var results: [AnyObject] = []

do {
    results = try managedObjectContext.executeFetchRequest(fetchRequest)
} catch let error {
    NSLog("Error fetching data: \(error)")
}

if let tags = results as? [Tag] {
    for tag in tags {
        NSLog("Tag: \(tag.name)")

        if let contentSet = tag.content {
            for content in contentSet { // Crashes here when trying to access the first member of the set
                NSLog("    * Content: \(content)")
            }
        }
    }
}

Upvotes: 0

Views: 908

Answers (2)

user3163025
user3163025

Reputation:

It looks like I had initialization of my Core Data stack out of order. I am using a multi-context setup; I have a private queue context attached to the persistent store and a main queue context for doing all my regular work with the private context set as its parent. I'm also initializing my persistent store in a background thread as outlined in the Core Data book.

My initialization code originally looked like this:

let optionalManagedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL.NSURL)
assert(nil != optionalManagedObjectModel, "Failed to initialize managed object model for model URL \(modelURL.absoluteString)")

let managedObjectModel         = optionalManagedObjectModel!
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
let mainContext                = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
let privateContext             = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)

mainContext.parentContext                 = privateContext
privateContext.persistentStoreCoordinator = persistentStoreCoordinator

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
    do {
        try persistentStoreCoordinator.addPersistentStoreWithType(storeType, configuration: nil, URL: persistentStoreURL!.NSURL, options: nil)
    } catch let error as NSError {
        NSLog("Error initializing persistent store: \(error)")
    } catch {
        fatalError()
    }

    dispatch_async(dispatch_get_main_queue()) {
        completion(mainContext) // handles finishing up app launch with data from the main context
    }
}

If I delay setting the parent context of mainContext until after the persistent store is configured then everything works as expected:

let optionalManagedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL.NSURL)
assert(nil != optionalManagedObjectModel, "Failed to initialize managed object model for model URL \(modelURL.absoluteString)")

let managedObjectModel         = optionalManagedObjectModel!
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
let mainContext                = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
let privateContext             = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)

privateContext.persistentStoreCoordinator = persistentStoreCoordinator

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
    do {
        try persistentStoreCoordinator.addPersistentStoreWithType(storeType, configuration: nil, URL: persistentStoreURL!.NSURL, options: nil)
        mainContext.parentContext = privateContext // do this after persistent store is set up
    } catch let error as NSError {
        NSLog("Error initializing persistent store: \(error)")
    } catch {
        fatalError()
    }

    dispatch_async(dispatch_get_main_queue()) {
        completion(mainContext) // handles finishing up app launch with data from the main context
    }
}

So, it turns out that I was doing something wrong, although it was (and still is) not clear why this is necessary. The only thing I can think of is that there are tasks done when assigning a parent NSManagedObjectContext to a child context, but I can't find that it's documented anywhere. I suspect initializing the persistent store in the background may be a culprit as well. I have done this several times in Objective-C before without trouble, so it's curious that it would be a problem with Swift.

Upvotes: 1

Nathan Hillyer
Nathan Hillyer

Reputation: 1979

Did you remember to set the class for your entities?

enter image description here

Upvotes: 0

Related Questions