Last active
June 27, 2016 03:12
-
-
Save rnapier/5835b4014ec23663512e7daea61f217d to your computer and use it in GitHub Desktop.
How Self plays poorly with KVO
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: 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