Как использовать протокол relatedType в переменные или массивы через `some` в Swift 5.1? - PullRequest
1 голос
/ 23 февраля 2020

Я пытался привести массив или var в соответствие ProtocolA, но столкнулся с некоторыми ошибками.
Что здесь происходит?

Я создал два протокола с / без associatedtype и два struct соответствуют ProtocolA и ProtocolB

protocol ProtocolA {
    associatedtype ProtocolAType

    var prop1: ProtocolAType { get set }

    func description()
    func methodA(param1: ProtocolAType) -> ProtocolAType
}

protocol ProtocolB {
    func description()
}

extension ProtocolA {
    func description() {
        print(self)
    }

    func methodA(param1: ProtocolAType) -> ProtocolAType {
        param1
    }
}

struct StructA: ProtocolA, ProtocolB {
    var prop1: Int
}

struct StructB: ProtocolA, ProtocolB {
    var prop1: String
}

Я создал CustomCollection для прохождения некоторого типа.

struct CustomCollection<T> {
    typealias Items = [T]

    private var items: Items

    init(items: Items) {
        self.items = items
    }
}

extension CustomCollection: ExpressibleByArrayLiteral {
    init(arrayLiteral elements: T...) {
        self.items = elements
    }
}

extension CustomCollection: Collection {
    typealias Index = Items.Index
    typealias Element = Items.Element

    var startIndex: Index { items.startIndex }
    var endIndex: Index { items.endIndex }

    subscript(index: Index) -> Iterator.Element {
        get { items[index] }
        set { items[index] = newValue }
    }

    func index(after i: Index) -> Index {
        items.index(after: i)
    }
}

CustomCollection работает нормально с протоколами не связанных типов.

var items: CustomCollection<ProtocolB> = [StructA(prop1: 1), StructB(prop1: "1")]
for item in items {
    item.description()
}

Я пытался позвонить methodA, но у меня есть ошибки ниже.

var item1: some ProtocolA = StructA(prop1: 1)
var item2: some ProtocolA = StructB(prop1: "1")

item1.description()

//Cannot invoke 'methodA' with an argument list of type '(param1: Int)'
var result1 = item1.methodA(param1: 1)
//Cannot invoke 'methodA' with an argument list of type '(param1: String)'
var result2 = item2.methodA(param1: "1")

Не знаю, как сделать [ProtocolA]

//Cannot convert value of type '[Any]' to specified type 'some ProtocolA'
//Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
var items1: some ProtocolA = [StructA(prop1: 1), StructB(prop1: "1")]
//Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
//Return type of var 'items2' requires that '[StructA]' conform to 'ProtocolA'
var items2: some ProtocolA = [StructA(prop1: 1), StructA(prop1: 1)]




Я бы хотел позвонить methodA.

for item in items1 {
    item.methodA(2)
}
for item in items2 {
    item.methodA("2")
}

Я не знаю, как указать associatedtype

//Protocol 'ProtocolA' can only be used as a generic constraint because it has Self or associated type requirements
var items4: CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]
//An 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class
var items5: some CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]

Я не хотел бы использовать кастинг на сайте вызовов, как показано ниже

var items: some Collection = [StructA(prop1: 1), StructB(prop1: "1")]
for item in items {
    if let item = item as? StructA {
        item.methodA(param1: 4)
    }
    if let item = item as? StructB {
        item.methodA(param1: "3")
    }
}

Я хотел бы использовать что-то вроде ниже

var items: some CustomCollection<ProtocolA> = [StructA(prop1: 1), StructA(prop1: 2)]
for item in items {
    item.methodA(param1: 4)
}

Полагаю, мне придется создавать разные протоколы для всех поддерживаемых типов без связанных типов. Это один путь или есть другой?

1 Ответ

1 голос
/ 23 февраля 2020

К сожалению, вы просто не можете.

Объяснение довольно простое, на самом деле, для Swift ваш массив должен содержать элементы одного и того же "типа". Когда у вашего протокола нет ассоциированного типа, это просто, ваш массив содержит ProtocolB экземпляров ...

Но когда задействован связанный тип (или Self), ProtocolA недостаточно. Поскольку ProtocolA ничего не значит отдельно, ему нужен связанный с ним тип. Точнее, вы не можете иметь в одном массиве ProtocolA{Int} и ProtocolA{String}.

Так что, когда вы объявляете переменную или массив ProtocolA, компилятор не может знать, что это за связанный тип .. связаны.

Это то же самое, что и дженерики:

var x : Array = [3,4]
x = ["thing"] // You can't

Первая строка выводит тип на Array<Int>, поэтому вы не можете присвоить массив строк ...

Но когда вы пишете:

var items: CustomCollection<ProtocolA> = [StructA(prop1: 1)]

Это не может быть выведено, потому что "реальный" тип, который ему понадобится, будет CustomCollection<ProtocolA{Int}>, но в swift такого нет ...

Хуже, когда вы пишете:

var items: CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]

У вас есть элементы ProtocolA{Int} и ProtocolA{String}.

Примечание : Когда я пишу ProtocolA{Int} это просто способ уточнить, что самому протоколу нужен связанный с ним тип, а не какой-то синтаксис.

РЕДАКТИРОВАТЬ : На ваш последний вопрос, вы не можете сделать что-то вроде что?

protocol ProtocolAA {
    func description()
}

extension ProtocolAA {
    func description() {
        print(self)
    }

    func methodA<T>(param1: T) -> T {
        param1
    }
}

struct StructA: ProtocolAA, ProtocolB {
    var prop1: Int
}

struct StructB: ProtocolAA, ProtocolB {
    var prop1: String
}

var items: CustomCollection<ProtocolAA> = [StructA(prop1: 1), StructA(prop1: 2), StructB(prop1: "X")]
for item in items {
    item.methodA(param1: 4)
}
...