2016-09-19 4 views
2

Возможно ли, чтобы общий метод выводил его тип на основе класса, в котором он выполняется? Я использую модели CoreData NSManagedObject для хранения и извлечения локальных данных и сумел сделать все общее с легко читаемым и удобным способом, за исключением одного места. Если пользователь желает запросить локальную базу данных для извлечения списка объектов, он будет написать следующую строку:Infer Generic Type in Class Method with Swift

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

Это будет правильно вернуть «все» почтовые объекты в базе данных, но синтаксис требует, чтобы тип ([Post]) поверх вызова метода из самого класса Post (Post.all()), который чувствует излишнюю избыточность. Есть ли способ определить общий тип, просто вызвав метод all() из класса Post? Я полагаю, я мог бы просто создать глобальные функции для извлечения данных, например, так:

let posts: [Post] = all() 

Это не чувствует себя почти так же читаемым, как было бы, если синтаксис следующим образом:

let posts = Post.all() 

Точка попытки улучшить это так, чтобы любые разработчики, которые подбирали этот проект, могли быстро изучить структуру и стиль без особых усилий. Кроме того, это, мы надеемся, повысит общую читаемость кода в будущем, независимо от того, работает ли кто-то над этим или просто читает его по какой-то другой причине.

Для более глубокого, здесь немного больше информации о текущей структуре:

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

Если кто-то имеет представление о том, пожалуйста, дайте мне знать. Вся помощь приветствуется!

ответ

5

В общем случае типичным способом для метода класса возвращать «тип класса» даже для подклассов является использование расширений протокола и типа Self. Вот пример, который кипит ваш подход вниз к минимуму, чтобы сделать проверку типа работать так, как вы хотите:

// 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]?` 

Обратите внимание, что all() должен быть обеспечен расширением протокола, но не требования протокола , Если вы объявите all() внутри protocol ModelType, то вы не можете использовать динамическую отправку, что необходимо, если вы хотите использовать динамический тип.


Кроме того, обратите внимание, что в Swift 3 (и MacOS 10,12/IOS 10/tvOS 10/watchOS 3), Core Data сам определяет некоторые Swift ярлыки API, которые заменяют некоторые из тех, которые вы определили для себя. Обратите внимание, этот пример из 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)") 
     } 
    }) 
} 

Наконец, некоторые комментарии по вашему выбору стиля ...

FYI Я заглавной буквы всех статических/методов класса как конвенции

Дело в попытке улучшить это так, что любые разработчики, которые подбирают этот проект может быстро изучить структуру и стиль без особых усилий. Кроме того, это будет надеяться, повысит общую читаемость кода в будущем

Я не уверен, что нарушение от языка стандартных конвенций (например, в нижнем регистре имен методов, рекомендованных в Swift 3 API Guidelines) очень совместима с вашей целью сделать его легко для других разработчиков, новых для вашей кодовой базы, чтобы читать и участвовать.

+0

Хорошая информация. Позвольте мне попробовать. Кроме того, RE: стиль кода - я это рассмотрю. Сама конвенция появилась только от работы в Свифте с другими людьми, в любом случае, где это был стандарт. Однако я не работаю с ними над этим проектом, поэтому я полагаю, что нет оснований устанавливать его, если языковые рекомендации указывают на другой путь. –

+0

Одна вещь, которую я видел, когда я начинаю реализовывать это, заключается в том, что 'let request = Animal.fetchRequest' должен быть' let request = Animal.fetchRequest() '; В случае, если другие люди придут к этому ответу, это может помочь отредактировать ответ с этим исправлением. –