Гетерогенная смесь типов протоколов, включая общий протокол

 protocol ParentProtocol { }

 protocol ChildProtocol: ParentProtocol { }

 protocol Child_With_Value_Protocol: ParentProtocol {
     associatedType Value

     func retrieveValue() -> Value
 }

Попытка создать один массив типа ParentProtocol который содержит оба ChildProtocol а также Child_With_Value_Protocol, Есть ли какой-нибудь возможный способ создать функцию, которая проходит через гетерогенный массив и возвращает значения просто типа Child_With_Value_Protocol?

Это может потребовать изменения архитектуры. Открыто для всех решений.

Попытка неудачного решения № 1

var parents: [ParentProtocol] = [...both ChildProtocol & Child_With_Value_Protocol...]

func retrieveValues() -> [Any] {
    var values = [Any]()

    for parent in parents {
        if let childWithValue = parent as? Child_With_Value_Protocol { // Fails to compile
            values.append(childWithValue.retrieveValue())
        }
    }

    return values
}

Это не с ошибкой protocol 'Child_With_Value_Protocol' can only be used as a generic constraint because it has Self or associated type requirements что имеет смысл, так как компилятор не будет знать тип при преобразовании в просто Child_With_Value_Protocol, это приводит к следующему неудачному решению.

Попытка неудачного решения № 2

Если массив был однородным массивом просто Child_With_Value_ProtocolСтирание типа может быть использовано для получения значений.

var parents: [ParentProtocol] = [...both ChildProtocol & Child_With_Value_Protocol...]

struct AnyValue {
    init<T: Child_With_Value_Protocol>(_ protocol: T) {
        _retrieveValue  = protocol.retrieveValue as () -> Any
    }

    func retrieveValue() -> Any { return _retrieveValue() }

    let _retrieveValue: () -> Any
}

func retrieveValues() -> [Any] {
    var values = [Any]()

    for parent in parents {
        values.append(AnyValue(parent).retrieveValue()) // Fails to compile
    }

    return values
}

Это не компилируется из-за того, что структура AnyValue не имеет инициализатора для ParentProtocol,

Попытка неудачного решения № 3

struct AnyValue {
    init<T: Child_With_Value_Protocol>(_ protocol: T) {
        _retrieveValue  = protocol.retrieveValue as () -> Any
    }

    func retrieveValue() -> Any { return _retrieveValue() }

    let _retrieveValue: () -> Any
}

var erased: [AnyValue] = [AnyValue(...), AnyValue(...), AnyValue(...)]

func retrieveValues() -> [Any] {
    var values = [Any]()

    for value in erased {
        values.append(value.retrieveValue())
    }

    return values
}

В отличие от других решений, это решение на самом деле компилируется. Проблема с этим решением заключается в том, что массив erased может содержать только значения стертых версий Child_With_Value_Protocol, Цель состоит в том, чтобы массив содержал типы обоих Child_With_Value_Protocol а также ChildProtocol,

Попытка Неудачного Решения № 4

Изменение структуры type-erase для включения инициализатора для ParentProtocol по-прежнему создает решение, которое компилируется, но тогда структура будет использовать только менее конкретный init вместо более конкретного init.

struct AnyValue {
    init?<T: ParentProtocol>(_ protocol: T) {
        return nil
    }

    init?<T: Child_With_Value_Protocol>(_ protocol: T) {
        _retrieveValue  = protocol.retrieveValue as () -> Any
    }

    func retrieveValue() -> Any { return _retrieveValue() }

    let _retrieveValue: (() -> Any)?
}

1 ответ

Предыдущие комментарии, вероятно, правы. Тем не менее, вы можете поместить варианты в перечисление и создать их массив. Ссылка будет затем включать значение перечисления, каждое из которых имеет связанные данные правильного типа

РЕДАКТИРОВАТЬ: я не беспокоился о связанных значениях, потому что он кажется не имеет отношения к задаваемому вопросу. На детской площадке работают следующие работы:

protocol ParentProtocol: CustomStringConvertible {
    static func retrieveValues(parents: [FamilyBox]) -> [ParentProtocol]
}

protocol ChildProtocol: ParentProtocol { }

protocol Other_Child_Protocol: ParentProtocol { }

enum FamilyBox {
    case Parent(parent: ParentProtocol)
    case Child(child: ChildProtocol)
    case OtherChildProtocol(withValue: Other_Child_Protocol)

}


var parents: [FamilyBox] = []

struct P: ParentProtocol {
    var description: String { return "Parent" }

    static func retrieveValues(parents: [FamilyBox]) -> [ParentProtocol] {
        var values = [ParentProtocol]()

        for parent in parents {
            switch parent {
            case .Parent(let elementValue):
                values.append(elementValue)
            default:
                break;
            }
        }
        return values
    }
}

struct C: ChildProtocol {
    var description: String { return "Child" }

    static func retrieveValues(parents: [FamilyBox]) -> [ParentProtocol] {
        var values = [ParentProtocol]()

        for parent in parents {
            switch parent {
            case .Child(let elementValue):
                values.append(elementValue)
            default:
                break;
            }
        }
        return values
    }
}

struct CV: Other_Child_Protocol {
    var description: String { return "Other Child" }

    static func retrieveValues(parents: [FamilyBox]) -> [ParentProtocol] {
        var values = [ParentProtocol]()

        for parent in parents {
            switch parent {
            case .OtherChildProtocol(let elementValue):
                values.append(elementValue)
            default:
                break;
            }
        }
        return values
    }
}

let p = FamilyBox.Parent(parent: P())
let c = FamilyBox.Child(child: C())
let cv = FamilyBox.OtherChildProtocol(withValue: CV())
let array:[FamilyBox] = [p, c, cv]

print(P.retrieveValues(array))
print(C.retrieveValues(array))
print(CV.retrieveValues(array))

Отпечатки из последних трех строк:

[Parent]
[Child]
[Other Child]

Хотя я уверен, что это может быть улучшено, я думаю, что это соответствует первоначальному замыслу. Нет?

Другие вопросы по тегам