To understand the origin of the question, let's start with some code:
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
}
And then I have a struct with a heterogeneous array of type MyProtocol
:
struct Values {
var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0)]
}
if I was to change one of the values with a method in Values
such as:
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
}
}
That would be smooth. The problem which I am facing is, say I wanted to change var thirdProperty: Int
in the structB
item in var arr: [MyProtocol]
, I would not be able to do so which my mutating func set<T: MyProtocol>(at index: Int, _ newValue: T)
, since It only knows of MyProtocol
types.
So my 2 cents to resolve this matter was using a closure something like this:
mutating func set<T: MyProtocol>(at index: Int, closure: (T?) -> (T)) {
arr[index] = closure(arr[index] as? T)
}
The problem with this is that every time I invoke this method, I would first need to downcast the parameter (from MyProtocol
to StructB
). which seems more of a workaround which could invite unwanted behaviours along the road.
So I started thinking maybe there is a way to constraint the generic parameter to a sibling parameter something like this (pseudo code):
mutating func set<T: MyProtocol>(type: MyProtocol.Type, at index: Int, closure: (T?) -> (T)) where T == type {
arr[index] = closure(arr[index] as? T)
}
Which as you guessed, does not compile.
Any thought on how to approach this matter in a better manner. TIA
Use T.Type
instead of MyProtocol.Type
in the set(type:at:closure:)
method.
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
}
}
}
Example:
var v = Values()
v.set(type: StructB.self, at: 1) {
var value = $0
value?.thirdProperty = 20
return value
}
Do let me know if this is the right understanding of your requirement.
PGDev's solution gets to the heart of the question, but IMO the following is a bit easier to use:
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
}
The = T.self
syntax allows this to be simplified a little when the type is known:
func updateThirdProperty(v: inout StructB) {
v.thirdProperty = 20
}
try v.set(at: 1, applying: updateThirdProperty)
Another approach that is more flexible, but slightly harder on the caller, would be a closure that returns MyProtocol, so the updating function can modify the type. I'd only add this if it were actually useful in your program:
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
}
(Which is very close to PGDev's example, but doesn't require Optionals.)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.