0

要了解问题的起源,让我们从一些代码开始:

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)
 }

这样做的问题是,每次我调用这个方法时,我首先需要向下转换参数(从MyProtocolStructB)。这似乎更像是一种解决方法,可能会在路上引起不受欢迎的行为。

所以我开始思考也许有一种方法可以将泛型参数限制为类似这样的兄弟参数(伪代码):

 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

4

2 回答 2

2

在方法中使用T.Type而不是。MyProtocol.Typeset(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
}

如果这对您的要求有正确的理解,请告诉我。

于 2020-05-08T13:38:44.627 回答
1

PGD​​ev 的解决方案触及了问题的核心,但 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。)

于 2020-05-08T14:21:05.077 回答