Skip to content

Instantly share code, notes, and snippets.

@rnapier
Last active June 27, 2016 03:12
Show Gist options
  • Save rnapier/5835b4014ec23663512e7daea61f217d to your computer and use it in GitHub Desktop.
Save rnapier/5835b4014ec23663512e7daea61f217d to your computer and use it in GitHub Desktop.
How Self plays poorly with KVO
//: Playground - noun: a place where people can play
import Foundation
import CoreData
// I've been using Martin R's clever transferTo trick to move NSManagedObjects between contexts.
// http://stackoverflow.com/a/28233753/97337
// And it's super nice (slightly modified with names I like better):
func objcast<T>(obj: AnyObject) -> T {
return obj as! T
}
extension NSManagedObject {
func inContext(context: NSManagedObjectContext) -> Self {
let result = context.objectWithID(objectID) // NSManagedObject
return objcast(result) // Self
}
}
// This is called like
// let y = x.inContext(otherContext)
// which is pretty beautiful.
// But oh the perils of Self. So, what do you imagine happens here if this object has been KVO observed?
// Right. Then Self is actually a *subclass* of the class you meant. And the rsult of objectWithID() is not
// that subclass. So blam! The crash you get is:
// Could not cast value of type 'myapp.Thing' (0xdeadbeef) to 'myapp.Thing' (0xfdfdfdfd).
// Yeah, because there are two different classes in the system that claim to be Thing. Oh KVO, how Swift does
// love your magic ways. Of course the fact that we had to trick Swift into doing this in the first place (you
// can't just return Self) was a hint that maybe this wasn't as robust as we'd like, so it's not like the
// compiler didn't warn us
// So Kugler/Eggert solution (https://www.objc.io/books/core-data/) works, but it's not nearly as beautiful
// for this use case.
extension SequenceType where Generator.Element: NSManagedObject {
func remapToContext(context: NSManagedObjectContext) -> [Generator.Element] {
return map { unmappedMO in
guard unmappedMO.managedObjectContext !== context else { return unmappedMO }
guard let object = context.objectWithID(unmappedMO.objectID) as? Generator.Element else { fatalError("Invalid object type") }
return object
}
}
}
// To remap a single object, you wind up doing something like
// let y = [x].remapToContext(otherContext).first!
// And that's a cure worse than the disease.
// The problem is that I want Self to be the statically determined type, not the dynamically determined type.
// The answer is simple, though. Just avoid Self. Move the work to the context:
extension NSManagedObjectContext {
func transferredObject<T: NSManagedObject>(object: T) -> T {
return objectWithID(object.objectID) as! T
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment