Концептуальное обсуждение
Ваша концепция архитектуры ошибочна, и это приводит к вашей проблеме.
Пример простого обобщенного примера
Вот очень простой пример универсальной функции, которая просто возвращает значение, которое вы ей даете:
func echo <T> (_ value: T) -> T { return value }
Поскольку эта функция является общей, существует неопределенность в отношении типа, который она использует. Что такое T
? Swift является языком, безопасным для типов, что означает, что в конечном счете не допускается никакой двусмысленности относительно типа. Так почему же разрешена эта функция эха? Ответ заключается в том, что когда я на самом деле где-то использую эту функцию, неопределенность в отношении типа будет удалена. Например:
let myValue = echo(7) // myValue is now of type Int and has the value 7
В действии с использованием этой обобщенной функции я устранил неоднозначность, передав ей Int
, и поэтому компилятор не имеет никакой неопределенности относительно задействованных типов.
Ваша функция
func currentPageController <DataSource> (at index: Int) -> BookPageViewController<DataSource>
Ваша функция использует только общий параметр DataSource
в возвращаемом типе, а не во входных данных - как компилятор должен выяснить, что такое DataSource
? * Полагаю, именно так вы и представляли используя вашу функцию:
let pager = Pager()
let controller = pager.currentPageController(at: 0)
Но теперь, какой тип controller
? Что вы можете ожидать от этого? Кажется, вы надеетесь, что controller
примет правильный тип на основе значения , которое вы передаете (0
), но это не так. Общий параметр определяется на основе типа ввода, а не значения ввода. Вы надеетесь, что передача 0
даст один тип возврата, в то время как 1
даст другой тип, но это запрещено в Swift. И 0
, и 1
относятся к типу Int
, а type - это все, что может иметь значение.
Как обычно бывает в Swift, это не язык / компилятор, который мешает вам что-то делать. Дело в том, что вы еще не сформулировали логически то, что вы хотите, и компилятор просто информирует вас о том, что то, что вы написали до сих пор, не имеет смысла.
Решения
Давайте перейдем к тому, чтобы дать вам решение.
Функциональность UIViewController
Предположительно есть что-то, для чего вы хотели бы использовать controller
. Что вам на самом деле нужно? Если вы просто хотите вставить его в навигационный контроллер, вам не нужно, чтобы он был BookPageViewController
. Вам нужно только, чтобы он был UIViewController
, чтобы использовать эту функцию, поэтому ваша функция может стать такой:
func currentPageController (at index: Int) -> UIViewController {
if index == 0 {
return readingPageController()
}
return starsPageController()
}
И вы можете поместить контроллер, который он возвращает, в стек навигации.
Пользовательская функциональность (не универсальная)
Если, однако, вам нужно использовать некоторые функции, специфичные для BookPageViewController
, то это зависит от того, что вы хотите сделать. Если есть метод BookPageViewController
, подобный этому:
func doSomething (input: Int) -> String
, который не использует универсальный параметр DataSource
, тогда, вероятно, вы захотите выделить эту функцию в свой собственный протокол / суперкласс, который не является универсальным. Например:
protocol DoesSomething {
func doSomething (input: Int) -> String
}
и затем BookPageViewController
соответствуют этому:
extension BookPageViewController: DoesSomething {
func doSomething (input: Int) -> String {
return "put your implementation here"
}
}
Теперь типом возврата вашей функции может быть этот неуниверсальный протокол:
func currentPageController (at index: Int) -> DoesSomething {
if index == 0 {
return readingPageController()
}
return starsPageController()
}
и вы можете использовать его так:
let pager = Pager()
let controller = pager.currentPageController(at: 0)
let retrievedValue = controller.doSomething(input: 7)
Конечно, если тип возвращаемого значения больше не является UIViewController
, тогда вы, вероятно, захотите рассмотреть переименование функции и связанных с ней переменных.
Пользовательские функции (общие)
Другой вариант заключается в том, что вы не можете выделить необходимую функциональность в неуниверсальный протокол / суперкласс, поскольку эта функция использует универсальный параметр DataSource
. Базовый пример:
extension BookPageViewController {
func setDataSource (_ newValue: DataSource) {
self.dataSource = newValue
}
}
Так что в этом случае вам действительно нужно, чтобы возвращаемый тип вашей функции был BookPageViewController<DataSource>
. Чем ты занимаешься? Что ж, если вы действительно хотите использовать метод setDataSource(_:)
, определенный выше, то у вас должен быть объект DataSource
, который вы планируете передать в качестве аргумента, верно? Если это так, то мы делаем успехи. Раньше у вас было только какое-то значение Int
, которое вы передавали в вашу функцию, и проблема заключалась в том, что вы не могли указать с этим свой тип возвращаемого значения. Но если у вас уже есть значение BookPageDataSource
, то, по крайней мере, логически возможно использовать его для специализации вашего
функция.
Однако вы говорите, что хотите, просто использовать Int
, чтобы получить контроллер с этим индексом, независимо от типа DataSource
. Но если вас не волнует, что DataSource
соответствует возвращенному BookPageViewController
, то как вы можете ожидать установить DataSource
в другое значение, используя метод setDataSource(_:)
?
Видите ли, проблема решается сама собой. Единственная причина, по которой вам нужно, чтобы возвращаемый тип вашей функции был универсальным, заключается в том, что последующая функциональность, которую вам необходимо использовать, использует этот универсальный тип, но если это так, то у возвращаемого вами контроллера не может быть просто какой-либо старой DataSource
(вы просто хотели, чтобы тот соответствовал индексу, который вы предоставляете) - вам нужно, чтобы он точно соответствовал типу DataSource
, который вы планируете передать при его использовании, иначе вы дадите ему неправильный тип.
Итак, окончательный ответ на ваш вопрос заключается в том, что в том виде, в каком вы его представляли, нет возможности использовать функцию, которую вы пытались создать. Что очень круто в том, как Swift спроектирован, так это то, что компилятор на самом деле способен выявить этот логический недостаток и помешать вам создавать код, пока вы не переосмыслили его.
Сноска:
* Можно использовать универсальную функцию, которая использует универсальный параметр только в типе возвращаемого значения, а не во входных данных, но это вам здесь не поможет.