How to use generic types to get object with same type

老子叫甜甜 提交于 2019-11-28 00:13:49
Martin R

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.)

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.

Jean-Philippe Pellet

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)
    }

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!