6

I have extension for NSManagedObject that should help me to transfer objects between contexts:

extension NSManagedObject {

    func transferTo(#context: NSManagedObjectContext) -> NSManagedObject? {

        return context.objectWithID(objectID)
    }

}

for now it return object of NSManagedObject and i should cast it to class what i want, like this:

let someEntity: MyEntity = // ...create someEntity
let entity: MyEntity = someEntity.transferTo(context: newContext) as? MyEntity

Is there a way in Swift to avoid that useless casting and if i call transferTo(context: ...) from object of class MyEntity make it return type to MyEntity?

Oleg_Korchickiy
  • 298
  • 5
  • 15

3 Answers3

3

I've liked Martin's solution for a long time, but I recently ran into trouble with it. If the object has been KVO observed, then this will crash. Self in that case is the KVO subclass, and the result of objectWithID is not that subclass, so you'll get a crash like "Could not cast value of type 'myapp.Thing' (0xdeadbeef) to 'myapp.Thing' (0xfdfdfdfd)." There are two classes that call themselves myapp.Thing, and as! uses the actual class object. So Swift is not fooled by the noble lies of KVO classes.

The solution is to replace Self with a static type parameter by moving this to the context:

extension NSManagedObjectContext {
    func transferredObject<T: NSManagedObject>(object: T) -> T {
        return objectWithID(object.objectID) as! T
    }
}

T is purely defined at compile-time, so this works even if object is secretly a subclass of T.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • @MartinR For the slightly longer version, see https://gist.github.com/rnapier/5835b4014ec23663512e7daea61f217d – Rob Napier Jun 14 '16 at 16:05
  • I can confirm both the problem and your solution! – Martin R Jun 14 '16 at 20:29
  • I've been trying to solve this same problem, and both my code and yours—using static parameters, not `Self`—run into an issue in optimized builds. Have you seen anything like that happen? http://stackoverflow.com/questions/39109373/core-data-swift-cast-failure-in-generic-function-in-optimized-builds – Sixten Otto Aug 23 '16 at 21:12
2

This will do the trick:

func transferTo(#context: NSManagedObjectContext) -> Self?

At call site, Self resolves to the statically known type of the object you're calling this method on. This is also especially handy to use in protocols when you don't know the final type that will conform to the protocol but still want to reference it.

Update: Martin R's answer points out that you can't cast the obtained object right away. I'd then do something like this:

// top-level utility function
func cast<T>(obj: Any?, type: T.Type) -> T? {
    return obj as? T
}

extension NSManagedObject {

    func transferTo(#context: NSManagedObjectContext) -> NSManagedObject? {
        return cast(context.objectWithID(objectID), self.dynamicType)
    }

}
Community
  • 1
  • 1
Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
  • That's what I tried first, but you somehow have to cast the return value from `context.objectWithID(objectID)`, which is `NSManagedObject`, to the actual return type, and `as? Self` or similar does not compile (or I could not figure out how). – Martin R Jan 30 '15 at 10:26
  • 2
    Now we have a retain cycle between our answers :) For this particular case where we already *know* that the object is of the desired type, an unsafeBitCast() would work as well. – Martin R Jan 30 '15 at 12:20
  • Nice!, but IMO, the parameter should be `AnyObject` for 2 reasons. 1) `Any` occupies 32bit memory, conversion from a class instance to `Any` costs CPU and memory. 2) Unlike `Any`, we can not pass `Optional` to `AnyObject` parameter, it's safer than `Any`. – rintaro Jan 30 '15 at 12:47
  • It seems to me that actually two functions are needed: `func cast(obj: AnyObject?, type: T.Type) -> T?` and `func cast(obj: AnyObject, type: T.Type) -> T`. – Martin R Jan 30 '15 at 12:54
  • correction: "`Any` occupies 32bit" → "`Any` occupies 32**bytes**" – rintaro Jan 30 '15 at 14:09
2

Update: For a better solution, see Rob's answer.


Similarly as in How can I create instances of managed object subclasses in a NSManagedObject Swift extension?, this can be done with a generic helper method:

extension NSManagedObject {

    func transferTo(context context: NSManagedObjectContext) -> Self {
        return transferToHelper(context: context)
    }

    private func transferToHelper<T>(context context: NSManagedObjectContext) -> T {
        return context.objectWithID(objectID) as! T
    }
}

Note that I have changed the return type to Self. objectWithID() does not return an optional (in contrast to objectRegisteredForID(), so there is no need to return an optional here.

Update: Jean-Philippe Pellet's suggested to define a global reusable function instead of the helper method to cast the return value to the appropriate type.

I would suggest to define two (overloaded) versions, to make this work with both optional and non-optional objects (without an unwanted automatic wrapping into an optional):

func objcast<T>(obj: AnyObject) -> T {
    return obj as! T
}

func objcast<T>(obj: AnyObject?) -> T? {
    return obj as! T?
}

extension NSManagedObject {

    func transferTo(context context: NSManagedObjectContext) -> Self {
        let result = context.objectWithID(objectID) // NSManagedObject
        return objcast(result) // Self
    }

    func transferUsingRegisteredID(context context: NSManagedObjectContext) -> Self? {
        let result = context.objectRegisteredForID(objectID) // NSManagedObject?
        return objcast(result) // Self?
    }
}

(I have updated the code for Swift 2/Xcode 7. The code for earlier Swift versions can be found in the edit history.)

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    Interesting! This would probably warrant the use of a global function like this maybe, instead of recreating helper methods when the need arises? `func cast(obj: Any, type: T.Type) -> T? { return obj as? T }` I've updated my answer to propose it. – Jean-Philippe Pellet Jan 30 '15 at 10:42
  • @Jean-PhilippePellet: Good idea, but the argument of `obj` should perhaps be `Any?`, otherwise it seems not to work correctly when casting optionals, such as the `NSManagedObject?` returned by `objectRegisteredForID()`. – Martin R Jan 30 '15 at 10:59
  • Oh yes, you're right, of course. I'm still too much thinking in Scala where non-optionals are not automatically wrapped. – Jean-Philippe Pellet Jan 30 '15 at 11:02
  • After using this for awhile, I discovered a fascinating problem. If you use it to transfer an object that has been KVO observed, it crashes. `Self` in that case is the KVO subclass, and the result of `objectWithID` is a superclass of that. So you get an error like "Could not cast value of type 'myapp.Thing' (0xdeadbeef) to 'myapp.Thing' (0xfdfdfdfd)." Still working on an elegant solution to that. – Rob Napier Jun 14 '16 at 14:49
  • Found a solution; added as an answer. – Rob Napier Jun 14 '16 at 15:41