Как ограничить параметр универсального типа в функции параметром родственного брака
Чтобы понять происхождение вопроса, давайте начнем с кода:
protocol MyProtocol {
var val1: Int { get set }
}
struct StructA: MyProtocol {
var val1: Int
var structAVal: Int
}
struct StructB: MyProtocol {
var val1: Int
var structBVal: Int
var thirdProperty: Int
}
И затем у меня есть структура с гетерогенным массивом типа MyProtocol
:
struct Values {
var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0)]
}
если бы мне пришлось изменить одно из значений с помощью метода в Values
Такие как:
struct Values {
var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0)]
mutating func set<T: MyProtocol>(at index: Int, _ newValue: T) {
arr[index] = newValue
}
}
Это было бы гладко. Проблема, с которой я столкнулся, - скажем, я хотел изменитьvar thirdProperty: Int
в structB
пункт в var arr: [MyProtocol]
, Я не смогу сделать это, mutating func set<T: MyProtocol>(at index: Int, _ newValue: T)
, поскольку он знает только о MyProtocol
типы.
Итак, мои 2 цента для решения этого вопроса использовали закрытие примерно так:
mutating func set<T: MyProtocol>(at index: Int, closure: (T?) -> (T)) {
arr[index] = closure(arr[index] as? T)
}
Проблема в том, что каждый раз, когда я вызываю этот метод, мне сначала нужно понижать значение параметра (с MyProtocol
к StructB
). что кажется скорее обходным путем, который может вызвать нежелательное поведение на дороге.
Итак, я начал думать, может быть, есть способ ограничить общий параметр параметром-братом примерно так (псевдокод):
mutating func set<T: MyProtocol>(type: MyProtocol.Type, at index: Int, closure: (T?) -> (T)) where T == type {
arr[index] = closure(arr[index] as? T)
}
Которая, как вы догадались, не компилируется.
Есть мысли о том, как лучше подойти к этому вопросу. TIA
2 ответа
Решение PGDev подходит к сути вопроса, но IMO немного проще в использовании:
enum Error: Swift.Error { case unexpectedType }
mutating func set<T: MyProtocol>(type: T.Type = T.self, at index: Int,
applying: ((inout T) throws -> Void)) throws {
guard var value = arr[index] as? T else { throw Error.unexpectedType }
try applying(&value)
arr[index] = value
}
...
var v = Values()
try v.set(type: StructB.self, at: 1) {
$0.thirdProperty = 20
}
В = T.self
синтаксис позволяет немного упростить это, если известен тип:
func updateThirdProperty(v: inout StructB) {
v.thirdProperty = 20
}
try v.set(at: 1, applying: updateThirdProperty)
Другой подход, который является более гибким, но немного сложнее для вызывающего, - это замыкание, которое возвращает MyProtocol, поэтому функция обновления может изменять тип. Я бы добавил это, только если бы это действительно было полезно в вашей программе:
mutating func set<T: MyProtocol>(type: T.Type = T.self, at index: Int,
applying: ((T) throws -> MyProtocol)) throws {
guard let value = arr[index] as? T else { throw Error.unexpectedType }
arr[index] = try applying(value)
}
...
try v.set(type: StructB.self, at: 1) {
var value = $0
value.thirdProperty = 20
return value // This could return a StructA, or any other MyProtocol
}
(Что очень похоже на пример PGDev, но не требует Optionals.)
Использовать T.Type
вместо того MyProtocol.Type
в set(type:at:closure:)
метод.
struct Values {
var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0, thirdProperty: 0)]
mutating func set<T: MyProtocol>(type: T.Type, at index: Int, closure: ((T?) -> (T?))) {
if let value = closure(arr[index] as? T) {
arr[index] = value
}
}
}
Пример:
var v = Values()
v.set(type: StructB.self, at: 1) {
var value = $0
value?.thirdProperty = 20
return value
}
Дайте мне знать, правильно ли вы понимаете ваши требования.