Массивы дженериков в Swift
я играл с массивами общих классов с различными типами. Проще всего объяснить мою проблему с помощью некоторого примера кода:
// Obviously a very pointless protocol...
protocol MyProtocol {
var value: Self { get }
}
extension Int : MyProtocol { var value: Int { return self } }
extension Double: MyProtocol { var value: Double { return self } }
class Container<T: MyProtocol> {
var values: [T]
init(_ values: T...) {
self.values = values
}
func myMethod() -> [T] {
return values
}
}
теперь, если я попытаюсь создать массив контейнеров, как так:
var containers: [Container<MyProtocol>] = []
Я получаю сообщение об ошибке:
протокол "MyProtocol" может использоваться только в качестве общего ограничения, поскольку он имеет собственные или связанные требования типа.
чтобы исправить это, я могу использовать [AnyObject]
:
let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]
// Explicitly stating the types just for clarity.
но теперь появляется еще одна "проблема" при перечислении через containers
:
for container in containers {
if let c = container as? Container<Int> {
println(c.myMethod())
} else if let c = container as? Container<Double> {
println(c.myMethod())
}
}
как вы можете видеть в коде выше, после определения типа container
в обоих случаях вызывается один и тот же метод. Мой вопрос:
есть ли лучший способ, чтобы получить Container
С правильным типом, чем литье для каждого возможного типа Container
? Или я проглядел что-то еще?
5 ответов
есть способ-своего рода - делать то, что вы хотите-вид. Есть способ, с протоколами, устранить ограничение типа и все равно получить результат, который вы хотите, но это не всегда красиво. Вот что я придумал в качестве протокола в вашей ситуации:
protocol MyProtocol {
func getValue() -> Self
}
extension Int: MyProtocol {
func getValue() -> Int {
return self
}
}
extension Double: MyProtocol {
func getValue() -> Double {
return self
}
}
отметим, что value
свойство, которое вы первоначально поместили в объявление протокола, было изменено на метод, который возвращает объект.
это не очень интересно.
но теперь, потому что вы избавились от value
собственность в протоколе, MyProtocol
может использоваться как тип, а не только как ограничение типа. Ваш Container
класс даже не должен быть общим больше. Вы можете объявить это так:
class Container {
var values: [MyProtocol]
init(_ values: MyProtocol...) {
self.values = values
}
func myMethod() -> [MyProtocol] {
return values
}
}
и так Container
больше не является общим, вы можете создать Array
of Container
s и повторите их, распечатав результаты myMethod()
способ:
var containers = [Container]()
containers.append(Container(1, 4, 6, 2, 6))
containers.append(Container(1.2, 3.5))
for container in containers {
println(container.myMethod())
}
// Output: [1, 4, 6, 2, 6]
// [1.2, 3.5]
фокус в том, чтобы построить протоколом только включает родовые функции и не устанавливает никакие другие требования на соответствуя типе. Если вам это сойдет с рук, вы можете использовать протокол как тип, а не только как ограничение типа.
и в качестве бонуса (если вы хотите назвать это, что), массив MyProtocol
значения могут даже смешивать различные типы, которые соответствуют MyProtocol
. Так что если вы даете String
a as MyProtocol }
}
}
теперь вы можете еще есть массив [ContainerProtocol]
объекты и перебирать его вызов myMethod()
:
let containers: [ContainerProtocol] = [Container(5, 3, 7), Container(1.2, 4,5)]
for container in containers {
println(container.myMethod())
}
может, это все еще не работает для вас, но теперь Container
ограничивается одним типом, и все же вы можете перебирать массив ContainterProtocol
объекты.
это хороший пример "что ты хочу произойдет?"И фактически демонстрирует сложность, которая взрывается, если у Swift действительно первоклассные типы.
protocol MyProtocol {
var value: Self { get }
}
большой. MyProtocol.value
возвращает любой тип реализует его, помня, что это должно быть определено во время компиляции, а не во время выполнения.
var containers: [Container<MyProtocol>] = []
Итак, определено во время компиляции, какой это тип? Забудьте о компиляторе, просто сделайте это на бумаге. Да, не уверен, что это будет за тип. Я имею в виду б тип. Нет metatypes.
let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)]
вы знаете, что идете по неправильному пути, когда AnyObject
пробрался в ваши подписи. Ничего из этого никогда не сработает. После AnyObject
только рубище.
или есть что-то еще, что я пропустил?
да. Вам нужен тип, А вы его не предоставили. Вы предоставили правило для ограничения типа, но не фактического типа. Вернитесь к своей настоящей проблеме и подумайте об этом более глубоко. (Анализ метатипов почти никогда не является вашей" реальной " проблемой, если вы не работаете над докторской диссертацией CS, и в этом случае вы будете делать это в Идрисе, а не Swift. Какую реальную проблему вы решаете?
это можно лучше объяснить с помощью таких протоколов, как Equatable
. Вы не можете объявить массив [Equatable]
потому что пока два Int
экземпляры можно сравнить друг с другом и два экземпляра Double
можно сравнить друг с другом, вы не можете сравнить Int
до Double
хотя они оба реализуют Equatable
.
MyProtocol
является протоколом, что означает, что он предоставляет общий интерфейс. К сожалению, вы также использовали Self
в определении. Это означает, что каждый тип что соответствует MyProtocol
будет реализовывать его по-другому.
вы сами это писали - Int
будет value
as var value: Int
а MyObject
будет value
as var value: MyObject
.
это означает, что структура / класс, который соответствует MyProtocol
не может использоваться вместо другой структуры / класса, который соответствует MyProtocol
. Это также означает, что вы не можете использовать MyProtocol
таким образом, без указания конкретного типа.
если вы замените, что Self
С конкретный тип, например,AnyObject
, он будет работать. Однако в настоящее время (Xcode 6.3.1) он вызывает ошибку сегментации при компиляции).
если вы попробуете этот измененный пример на игровой площадке, он будет систематически сбой:
// Obviously a very pointless protocol...
protocol MyProtocol {
var value: Int { get }
}
extension Int : MyProtocol { var value: Int { return self } }
//extension Double: MyProtocol { var value: Double { return self } }
class Container<T: MyProtocol> {
var values: [T]
init(_ values: T...) {
self.values = values
}
}
var containers: [Container<MyProtocol>] = []
вероятно, они все еще работают над этим, и все может измениться в будущем.
Во всяком случае, на данный момент мое объяснение заключается в том, что протокол не является конкретного типа. Таким образом, теперь вы не знаете, сколько места в ОЗУ займет что-то, соответствующее протоколу (например,Int
может не занимать столько же ОЗУ, сколько Double
). Таким образом, это может быть довольно проблема выделения массива в памяти.
С помощью NSArray
вы выделяете массив указателей (указателей на NSObjects
) и все они занимают одинаковое количество ОЗУ. Вы можете думать о NSArray
как массив конкретного типа "указатель на NSObject
". Таким образом, нет проблем с вычислением распределения ОЗУ.
считают, что Array
а также Dictionary
в Swift составляют Общая Структура, а не объекты, содержащие указатели на объекты как в В Obj-С.
надеюсь, что это помогает.
Я изменил объявление массива на массив AnyObject, чтобы можно было использовать фильтр, карту и уменьшить (а также добавить еще несколько объектов для проверки).
let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0), "Hello", "World", 42]
Это позволит вам проверить тип в массив и фильтровать, прежде чем цикл через массив
let strings = containers.filter({ return ( is String) })
println(strings) // [Hello, World]
for ints in containers.filter({ return ( is Int) }) {
println("Int is \(foo)") // Int is 42
}
let ints = containers.filter({ return ( is Container<Int>) })
// as this array is known to only contain Container<Int> instances, downcast and unwrap directly
for i in ints as [Container<Int>] {
// do stuff
println(i.values) // [1, 2, 3]
}