Generic Return Type Based on Class

2019-06-05 12:47发布

问题:

I'm trying to create factory method on a class that automatically casts to the class it's on.

extension NSObject {
    // how can I get the return type to be the current NSObject subclass
    // instead of NSObject?
    class func create() -> NSObject {
        return self.init()
    }

    // example: create(type: NSArray.self)
    class func create<T:NSObject>(type:T.Type) -> T {
        return T()
    }
}

Example two works, but gets NO advantage from being a class method:

let result = NSArray.create(type: NSArray.self) 

But I'd love to be able to just call:

let result = NSArray.create() 

without having to cast afterwards. Is there a way to do this in Swift?

回答1:

You can use the class-level Self for this:

extension NSObject {
    class func create() -> Self {
        return self.init()
    }
}

let array = NSArray.create()

But I don't really see why you would, since you might as well just add an initializer.



回答2:

The accepted answer does the trick, thanks!

However, I needed this for a case where I wasn't calling the init directly. Instead, I had an object that was of type NSObject and needed a forced downcast

As @Hamish pointed out from this other SO answer, you can use the generic inference on a class method if you're another layer deep (a method called by a class method).

class func create() -> Self {
    return createInner()
}

class func createInner<T>() -> T {
    // upcasting to NSObject to show that we can downcast
    let b = self.init() as NSObject
    return b  as! T
}

let array = NSArray.create() // gives me an NSArray

An Example with CoreData

I still can't figure out how to get the fetch part to compile, so I'm using an external function still.

import CoreData

// callers use
// try fetch(type: SomeMO.self, moc: moc)
func fetch<T:NSManagedObject>(type:T.Type, moc:NSManagedObjectContext) throws -> [T] {
    return try T.fetch(moc: moc) as! [T]
}

extension NSManagedObject {
    class func makeOne(moc:NSManagedObjectContext) -> Self {
        return makeOneInner(moc: moc)
    }

    private class func makeOneInner<T>(moc:NSManagedObjectContext) -> T {
        let name = "\(self)"
        let retVal = NSEntityDescription.insertNewObject(forEntityName: name, into: moc)
        return retVal as! T
    }

    class func fetch(moc:NSManagedObjectContext) throws -> [NSManagedObject] {
        let fetchReq:NSFetchRequest<NSManagedObject> = self.fetchRequest() as! NSFetchRequest<NSManagedObject>
        let retVal = try moc.fetch(fetchReq) as [NSManagedObject]
        return retVal
    }
}