Infer Generic Type in Class Method with Swift

白昼怎懂夜的黑 提交于 2020-02-01 08:49:11

问题


Is it possible for a generic method to infer its type based on the class in which it is being executed? I use CoreData NSManagedObject models to store and retrieve local data, and have managed to make everything generic in an easy to read and usable way, except for in one place. If a user wishes to query the local database to fetch a list of objects, he would write the following line:

let posts: [Post] = Post.all()

This will properly return "all" Post objects in the database, but the syntax requires that the type be defined ([Post]) on top of calling the method from the Post class itself (Post.all()), which feels unnecessarily redundant. Is there any way to define the generic type simply by calling the all() method from the Post class? I imagine I could just create global functions for fetching data, like so:

let posts: [Post] = all()

This doesn't feel nearly as readable as it would be if the syntax was as follows:

let posts = Post.all()

The point of trying to improve this is so that any developers who pick up this project can quickly learn the structure and style without much effort. Also, this will hopefully increase general code readability in the future, regardless of if someone is working on it or just reading it for some other reason.

For more insight, here is a bit more information about the current structure:

//Model.swift - The model base class. All models extend this class.

class Model: NSManagedObject {
    /**
    Some other stuff here
    **/

    //MARK: Fetch
    internal class func fetch<T: Model>(predicate: NSPredicate? = nil) -> [T]? {
        do {
            if let request = NSFetchRequest.FromEntityName(self.entityName) { //Get entity with the name defined in the current class
                request.predicate = predicate
                if let result = try self.context?.executeFetchRequest(request) as? [T] {
                    return result
                }
            }
        }
        catch let error as NSError {
            Log.Error("\(error)")
        }
        return nil
    }

    //MARK: Fetch general
    class func all<T: Model>() -> [T]? {
        if let result: [T] = self.fetch() {
            return result
        }
        Log.warning("No \(self.entityName) found")
        return nil
    }
}

//Post.swift - An example model class. Extends Model.swift

class Post: Model {
    //some fields
}

//Example view controller
class ViewController: UIViewController {
    override func viewDidLoad() {
        let posts: [Post] = Post.all()
        //do stuff
    }
}

If anyone has an idea about then please let me know. All help is appreciated!


回答1:


In the general case, the typical way for a class method to return "type of the class" even for subclasses is to use protocol extensions and the Self type. Here's an example that boils your approach down to the bare minimum to make the type checking work the way you want:

// define a protocol
protocol ModelType {}
// create a static method on the protocol that returns [Self]
extension ModelType where Self: NSManagedObject {
    static func all() -> [Self]? {
        return [Self]() // do your fetch here
    }
}
// conform to the protocol in your class hierarchy
class Model: NSManagedObject, ModelType {}
class Post: Model {}

let posts = Post.all()
// implicit type of `posts` is `[Post]?`

Note that all() should be provided by the protocol extension, but not a requirement of the protocol. If you declare all() inside protocol ModelType, then you can't make it use dynamic dispatch, which is necessary if it's to use a dynamic type.


Also, note that in Swift 3 (and macOS 10.12 / iOS 10 / tvOS 10 / watchOS 3), Core Data itself defines some Swift API shortcuts that replace some of the ones you've defined for yourself. Note this example from What's New in Core Data:

func findAnimals() {
    context.performAndWait({
        let request = Animal.fetchRequest // implicitly NSFetchRequest<Animal>
        do {
            let searchResults = try request.execute()
            // use searchResults ...
        } catch {
            print("Error with request: \(error)")
        }
    })
}

Finally, some commentary on your choice of style...

fyi I capitalize the first letter in all static/class methods just as a convention

The point of trying to improve this is so that any developers who pick up this project can quickly learn the structure and style without much effort. Also, this will hopefully increase general code readability in the future

I'm not sure that breaking from language-standard conventions (like the lowercase method names recommended in the Swift 3 API Guidelines) is very compatible with your goal of making it easy for other developers new to your codebase to read and participate.



来源:https://stackoverflow.com/questions/39582683/infer-generic-type-in-class-method-with-swift

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