Подтвердить что ты не робот

Дженерики в 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 и соответствует протоколу, поэтому никакого приведения не нужно, правильно?

4b9b3361

Ответ 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.