Массивы дженериков в 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 Containers и повторите их, распечатав результаты 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]
}