Дженерики в Swift - "Общий параметр T не мог быть выведен
Я хотел бы вернуть метод UIViewController
, соответствующий MyProtocol
из метода, поэтому я использую подпись метода:
func myMethod<T where T : UIViewController, T : MyProtocol>() -> T {
Первое, что я не понимаю: если myMethod
возвращается, например. a MyViewController
, который должен иметь следующую подпись, я должен заставить его выполнить:
class MyViewController: UIViewController, MyProtocol
Я не могу просто return MyViewController()
, но мне нужно сделать это следующим образом: return MyViewController() as! T
- зачем это нужно?
И второе: как я могу использовать этот метод где-то? Я не могу просто сказать
let x = myMethod() as? UIViewController
поскольку я получаю ошибку
Generic parameter 'T' could not be inferred
Как я могу добиться чего-то подобного? Если я отбрасываю его на MyViewController
, он работает, но я бы хотел этого избежать.
EDIT: Пример
class MyViewController : UIViewController, MyProtocol {
}
protocol MyProtocol {
}
func myMethod<T>() -> T where T : UIViewController, T : MyProtocol {
return MyViewController() as! T // why is the cast necessary?
}
ok, я получаю одну часть, но зачем нужен листинг T
? MyViewController
является подклассом UIViewController
и соответствует протоколу, поэтому никакого приведения не нужно, правильно?
Ответы
Ответ 1
func myMethod<T where T : UIViewController, T : MyProtocol>() -> T
В этом объявлении говорится: существует функция с именем myMethod
, так что myMethod
возвращает некоторый конкретный T
, где T
является подтипом UIViewController
, а также MyProtocol
. Это не говорит, какой тип T
на самом деле, и он не говорит, что существует только один такой myMethod
. Их может быть много, если существует много типов, которые являются подклассами UIViewController
и соответствуют MyProtocol
. Каждый из этих типов создает новую версию myMethod
(действительно новое решение для утверждения myMethod
делает, что такая функция существует).
Это не то же самое, что:
func myMethod() -> UIViewController
Это говорит: Функция myMethod
возвращает любой подтип UIViewController
.
В Swift нет способа выразить "любой тип, который является подклассом UIViewController и является подтипом MyProtocol". Вы можете обсудить только конкретный тип, соответствующий этому критерию. Swift не может сочетать классы и протоколы таким образом; это просто текущее ограничение языка, а не проблема с глубоким дизайном.
Конкретный против любого - проблема. Существует множество функций, которые удовлетворяют вашему объявлению myMethod
. Каждый T
, который вы можете подключить, который соответствует правилам, будет кандидатом. Поэтому, когда вы говорите myMethod()
, компилятор не знает, какой именно T
вы имеете в виду.
(Я собирался расширить этот ответ, чтобы предоставить его в меньшей теории типов, более "как это сделать с кодом", но у donnywals уже есть отличная версия этого.)
* К отредактированному вопросу *
func myMethod<T>() -> T where T : UIViewController, T : MyProtocol {
return MyViewController() as! T // why is the cast necessary?
}
T
- это определенный тип, выбранный вызывающим. Это не "любой тип, который соответствует", это "определенный конкретный, конкретный тип, который соответствует". Рассмотрим случай, который вы назвали:
let vc: SomeOtherViewController = myMethod()
В этом случае T
есть SomeOtherViewController
. MyViewController
не такой тип, поэтому то, что вы делаете с приложением as!
, опасно.
Ответ 2
В методе, подобном этому, возврат T
означает, что вы должны вернуть T
. Если вы вернетесь MyViewController
, тип возврата должен быть MyViewController
. T
- это общий тип, который будет принимать форму любого компилятора Swift, который он может сделать.
Итак, с вашей сигнатурой метода простая реализация протокола и метода может выглядеть так.
protocol MyProtocol {
var name: String { get set }
}
func myMethod<T where T : UIViewController, T : MyProtocol>() -> T {
var vc = T()
vc.name = "Hello, world"
return vc
}
Итак, учитывая ваш пример использования:
let x = myMethod()
Как компилятор узнает, что такое конкретный тип T
? Ничто не дает ему намека на MyViewController
. Единственное, что мы знаем, это то, что независимо от T
это должно быть MyViewController
или подкласс этого. И он должен соответствовать MyProtocol
. Но это не дает информации о типе T
.
Единственное место, где компилятор может заключить, что мы хотим T
быть, - через возвращаемое значение. Весь код между <>
является ограничением для того, что может быть T
. -> T
- это единственное место, где T
видно за пределами ограничений. Поэтому, если мы можем каким-то образом сообщить компилятору, что мы хотим вернуть myMethod
, мы дали ему достаточно информации для вывода T
.
Ваш тип работает, но я согласен, что это не очень красиво. Это гораздо более красивый способ для компилятора сделать вывод T
.
let vc: MyViewController = myMethod()
Указывая тип vc
, компилятор понимает, что мы хотим myMethod
вернуть MyViewController
. Итак, теперь тип T
может быть выведен, и если мы вернем T
, мы действительно вернем MyViewController
.