Гетерогенная смесь типов протоколов, включая общий протокол
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]
Хотя я уверен, что это может быть улучшено, я думаю, что это соответствует первоначальному замыслу. Нет?