Reputation:
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:
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
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