Вот мои структуры (у меня может быть гораздо больше, поэтому мне нужен общий способ работы с MyProtocol):
protocol MyCustomCodable {
static func buildQuery()
}
struct A: MyCustomCodable {
...
}
struct B: MyCustomCodable {
...
}
...
Тогда моя проблема: у меня где-то есть код, который я применяю к группе из них:
func updateAllData(customCodableArrays: [[some MyCustomCodable]]) {
for customCodableArray in customCodableArrays {
type(of: dbCodablesArray).Element.buildQuery()
}
}
let As: [A] = [A(), A()]
let Bs: [B] = [] // some arrays can be empty ! That's why buildQuery is static.
updateAllData([As, Bs])
Но здесь Swift жалуется, что при вызове updateAllData B не может быть преобразован в A.
Я также попробовал что-то вроде этого:
func updateAllData(customCodableArrays: [some MyCustomCodable]...) {
for customCodableArray in customCodableArrays {
type(of: dbCodablesArray).Element.buildQuery()
}
}
let As: [A] = [A(), A()]
let Bs: [B] = []
updateAllData([As, Bs])
Но «некоторые», похоже, не разрешены в вариациях.
Я пробовал с любым (или без него, поскольку это неявно):
func updateAllData(customCodableArrays: [[any MyCustomCodable]]) {
for customCodableArray in customCodableArrays {
type(of: dbCodablesArray).Element.buildQuery()
}
}
let As: [A] = [A(), A()]
let Bs: [B] = []
updateAllData([As, Bs])
Но здесь вызов updateAllData завершается неудачей, поскольку «статический член updateAllData не может использоваться в метатипе протокола (любой MyCustomCodable).Type»
Единственное решение, которое я нашел, — это передать массивы через отдельные аргументы в updateAllData с некоторым ключевым словом. Но это недопустимое решение, поскольку я не знаю, сколько таблиц мне понадобится, и это заставляет меня дублировать (более или менее) код для каждой из них (я не могу просто перебирать все массивы).
... Я не вижу, как решить мою проблему... Пожалуйста, помогите!
Я пробовал использовать Any, Some, Variadic, Any или casting.
buildQuery
реализован в расширении MyCustomCodable
и строит SQL-запрос на основе полей структуры. Содержимое массива не имеет значения (просто это то, что у меня есть в качестве исходных входных данных). Я пытался передать тип вместо экземпляра массива (извлекая его из массива), но это систематически вызывает ту же проблему, что и полученный тип any
, и блокирует использование buildQuery
. Если я приведу отдельные аргументы, как вы предлагаете, это сработает. Но, как уже упоминалось, я не знаю, сколько аргументов мне понадобится...
На самом деле я мог бы упростить вопрос о том, как вызвать статический метод протокола, имея тип любого протокола... Я понимаю, что означает любой, но не понимаю, почему это предполагает столь сильные ограничения в языке.
Значит, buildQuery вернет один и тот же результат для каждого экземпляра A, поскольку это статический метод?
Итак, buildQuery
действительно что-то возвращает? И я предполагаю, что в updateAllData
вы хотите получить все, что возвращают все buildQuery
, и поместить их в массив или что-то в этом роде?
buildQuery создаем запрос, смешивающий данные из всех структур (невозможно сделать отдельно), и запускаем его. Он ничего не возвращает.
Может быть, добавить пример, потому что я не понимаю, что он делает? «смешивание данных», что это значит, к каким данным вы получаете доступ из статического метода?
Вы правы, я путаю вещи, настоящий buildQuery возвращает элементы для построения запроса, касающиеся имени поля и типа типа структуры. Затем updateAll обрабатывает их вместе, чтобы создать и выполнить глобальный запрос. ... но я не думаю, что это имеет отношение к текущей проблеме. Проблема, с которой я столкнулся, - это то, с чем я сталкивался много раз, поскольку использую протоколы (не специфичные для этого варианта использования), и где я так и не нашел удовлетворительного решения.
Ваша фундаментальная проблема заключается в том, что [[some MyCustomCodable]]
определяет двумерный массив, в котором все элементы должны быть одного типа (что соответствует MyCustomCodable). Вот почему нельзя смешивать массивы типа [A]
и [B]
. Альтернатива [[any MyCustomCodable]]
определяет двумерный массив, любой элемент которого может иметь любой тип, соответствующий протоколу. Это бесполезно, потому что выражение type(of: MyCustomCodable).Element.buildQuery()
должно выбирать конкретную реализацию статической функции, а оно не может этого сделать, поскольку все элементы могут иметь разные соответствующие типы.
У меня другой подход, который требует немного больше работы, но имеет другие преимущества.
Ваш протокол остается прежним, но вы определяете другой протокол для коллекций MyCustomCodable
, который соответствует Collection
, а элементы должны быть ограничены в соответствии с MyCustomCodable
. то есть
protocol MyCustomCodable {
static func buildQuery()
}
protocol MyCustomCodableCollection: Collection
where Element: MyCustomCodable
{
func buildQuery()
}
У него есть функция-член под названием buildQuery()
Затем вы заставляете Array
соответствовать MyCustomCodableCollection
, что на самом деле очень просто, потому что Array
уже является Collection
. Вы можете реализовать это следующим образом:
extension Array: MyCustomCodableCollection where Element: MyCustomCodable
{
func buildQuery()
{
Element.buildQuery()
}
}
Тогда ваша функция обновления будет выглядеть так:
func updateAllData(customCodableArrays: [any MyCustomCodableCollection]) {
for customCodableArray in customCodableArrays {
customCodableArray.buildQuery()
}
}
Причина, по которой я говорю, что у него есть другие преимущества, заключается в том, что (в зависимости от того, что должен делать buildQuery()
) вы можете переместить этот код в функции static
в код функции соответствующего типа массива или использовать общие функции или какой-либо другой трюк, чтобы уберите функцию static
для каждого типа.
Хо! Отличное решение!!! Я попробую этот!
Я работаю! Ранее я пытался напрямую расширить Array следующим образом: extension Array where Element: MyCustomCodable { func buildQuery() -> QueryStructure { Element.buildQuery() } }
Но при попытке вызвать его возникла та же проблема... Теперь я понимаю, в чем была проблема. Большое спасибо
@GuillaumePELOUAS Также посмотрите Ответ Sweeper. Пакеты параметров появились сравнительно недавно и могут оказаться лучшим решением в зависимости от вашего варианта использования.
Для этого вы также можете использовать пакеты параметров.
func updateAllData<each T: MyCustomCodable>(customCodableArrays: repeat [each T]) {
repeat type(of: each customCodableArrays).Element.buildQuery()
}
updateAllData(customCodableArrays: As, Bs)
или, если содержимое массивов не имеет значения, возьмите метатипы напрямую:
func updateAllData<each T: MyCustomCodable>(types: repeat (each T).Type) {
repeat (each types).buildQuery()
}
updateAllData(types: A.self, B.self)
Если buildQuery
возвращает что-то, скажем, Query
, вы можете собрать их все в массив следующим образом:
func updateAllData<each T: MyCustomCodable>(types: repeat (each T).Type) {
var queries = [Query]()
repeat array.append((each types).buildQuery())
}
Привет, я не знал этого синтаксиса! Это довольно сложно, но решение не предполагает добавления протокола и расширения массива. Спасибо !
Имеет ли значение содержимое массивов? Как насчет просто
updateAllData(A.self, B.self)
? Что делаетbuildQuery
? Разве он не возвращает значение и имеет только побочные эффекты? Чтобы решить реальную проблему, вам понадобится пакет параметров, но я все равно задаю эти вопросы, потому что вы, возможно, идете в неправильном направлении.