amleszk amleszk - 6 months ago 31
Swift Question

How can I create instances of managed object subclasses in a NSManagedObject Swift extension?

When creating an extension helper to

NSManagedObject
to create a new managed object subclass, swift provides the
Self
type to mimic
instancetype
which is great, but i can't seem to typecast from
AnyObject
. The below code does not compile with error 'AnyObject' is not convertible to 'Self'

Help?

extension NSManagedObject
{
class func createInContext(context:NSManagedObjectContext) -> Self {
var classname = className()
var object: AnyObject = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context)
return object
}


class func className() -> String {
let classString = NSStringFromClass(self)
//Remove Swift module name
let range = classString.rangeOfString(".", options: NSStringCompareOptions.CaseInsensitiveSearch, range: Range<String.Index>(start:classString.startIndex, end: classString.endIndex), locale: nil)
return classString.substringFromIndex(range!.endIndex)
}

}

Answer

The trick is to use a generic helper method which infers the type of self from the context. Your className() method can also be simplified slightly, and a better name might by entityName():

extension NSManagedObject
{
    class func createInContext(context:NSManagedObjectContext) -> Self {
        return createInContextHelper(context)
    }

    private class func createInContextHelper<T>(context:NSManagedObjectContext) -> T {
        let classname = entityName()
        let object = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context) as! T
        return object
    }

    class func entityName() -> String {
        let classString = NSStringFromClass(self)
        // The entity is the last component of dot-separated class name:
        let components = split(classString, { $0 == "." })
        return components.last ?? classString
    }
}

Then

let obj = YourEntity.createInContext(context)

works and the compiler infers the type of obj correctly as YourEntity.


Update: Using the ideas from How to use generic types to get object with same type, this can also be done with a global reusable function instead of the helper method to cast the return value to the appropriate type:

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

extension NSManagedObject
{
    class func createInContext(context:NSManagedObjectContext) -> Self {
        let classname = entityName()
        let object: AnyObject = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context)
        return objcast(object)
    }

    class func entityName() -> String {
        let classString = NSStringFromClass(self)
        // The entity is the last component of dot-separated class name:
        let components = split(classString, { $0 == "." })
        return components.last ?? classString
    }
}

Update for Swift 1.2/Xcode 6.3 and Swift 2/Xcode 7:

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

extension NSManagedObject
{
    class func createInContext(context:NSManagedObjectContext) -> Self {
        let classname = entityName()
        let object: AnyObject = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context)
        return objcast(object)
    }

    class func entityName() -> String {
        let classString = NSStringFromClass(self)
        // The entity is the last component of dot-separated class name:
        let components = classString.componentsSeparatedByString(".")
        return components.last ?? classString
    }
}

Or with unsafeBitCast instead of a helper method, as suggested in the comments:

extension NSManagedObject
{
    class func createInContext(context:NSManagedObjectContext) -> Self {
        let classname = entityName()
        let object: AnyObject = NSEntityDescription.insertNewObjectForEntityForName(classname, inManagedObjectContext: context)
        return unsafeBitCast(object, self)
    }

    class func entityName() -> String {
        let classString = NSStringFromClass(self)
        // The entity is the last component of dot-separated class name:
        let components = classString.componentsSeparatedByString(".")
        return components.last ?? classString
    }
}
Comments