使用泛型枚举和泛型协议进行 Swift 类型擦除

5

我曾多次在Swift中使用类型擦除,但通常都涉及到通用协议。但在这种情况下,不仅涉及一个通用枚举还涉及到通用协议,让我很困惑。

以下是我的通用枚举和通用协议以及必要的扩展:

enum UIState<T> {
    case Loading
    case Success([T])
    case Failure(ErrorType)
}

protocol ModelsDelegate: class {
    associatedtype Model
    var state: UIState<[Model]> { get set }
}

extension ModelsDelegate {

    func getNewState(state: UIState<[Model]>) -> UIState<[Model]> {
        return state
    }

    func setNewState(models: UIState<[Model]>) {
        state = models
    }
}

这是我的类型擦除通用类:

class AnyModelsDelegate<T>: ModelsDelegate {
    var state: UIState<[T]> {

        get { return _getNewState(UIState<[T]>) }  // Error #1
        set { _setNewState(newValue) }
    }

    private let _getNewState: ((UIState<[T]>) -> UIState<[T]>)
    private let _setNewState: (UIState<[T]> -> Void)

    required init<U: ModelsDelegate where U.Model == T>(_ models: U) {
        _getNewState = models.getNewState
        _setNewState = models.setNewState
    }
}

我遇到了以下错误(它们在代码样本中被标记出来):
错误 #1:
无法将类型为 '(UIState<[T]>).Type'(即 'UIState<Array<T>>.Type')的值转换为预期的参数类型 'UIState<[_]>'(即 'UIState<Array<_>>')
我已经花了一段时间在这个问题上,并且已经有很多关于这段代码的变化“几乎可以工作”。这个错误总是与getter有关。

1
你正在将一个类型传递给一个接受该类型实例的方法。 - dan
我有一点困惑,为什么你的getNewState函数需要一个输入参数,它应该是一个() -> UIState <[Model]>吧?虽然如果你的getNewStatesetNewState函数仅仅是存在于你的类型擦除中以便转发获取和设置,那么它们并不是必要的,因为你可以使用闭包直接在类型擦除中完成这些操作(即 _getNewState = { models.state }_setNewState = { models.state = $0 })。 - Hamish
是的,我也有点困惑:)。它需要一个输入,因为最初在没有输入时会出现错误。让我尝试重构一下。 - damianesteban
谢谢,就这些了。请发布您的答案。 - damianesteban
1个回答

3

正如@dan所指出的,导致此错误的问题是,在这一行中,您试图传递一个类型作为参数,而不是该类型的实例:

get { return _getNewState(UIState<[T]>) }

然而,我对您在此函数中使用参数的做法表示质疑。毕竟,获取函数应该根本没有参数吧?在这种情况下,您只需要让_getNewState函数的签名为() -> UIState<[T]>,并按如下方式调用:

get { return _getNewState() }

此外,如果您在协议扩展中定义的getNewStatesetNewState(_:)函数只是为了将state属性的获取和设置转发给类型擦除,则可以通过完全摒弃它们并在类型擦除的init中使用闭包表达式来简化代码:
_getNewState = { models.state }
_setNewState = { models.state = $0 }

(这些通过捕获对models参数的引用来工作,更多信息请参见闭包:捕获值)

最后,我猜测你在代码中指的是 UIState<T> 而不是 UIState<[T]>,因为在这种情况下,T 指的是你的 .Success case 作为关联值具有的数组元素(除非你想要一个2D数组)。

总之,基于上述建议的更改,你需要让你的代码看起来像这样:

enum UIState<T> {
    case Loading
    case Success([T])
    case Failure(ErrorType)
}

protocol ModelsDelegate: class {
    associatedtype Model
    var state: UIState<Model> { get set }
}

class AnyModelsDelegate<T>: ModelsDelegate {
    var state: UIState<T> {
        get { return _getNewState() }
        set { _setNewState(newValue) }
    }

    private let _getNewState: () -> UIState<T>
    private let _setNewState: (UIState<T>) -> Void

    required init<U: ModelsDelegate where U.Model == T>(_ models: U) {
        _getNewState = { models.state }
        _setNewState = { models.state = $0 }
    }
}

非常好。谢谢你。是的,我不想要一个二维数组。那是我的错误,因为我试图解决错误。 - damianesteban
@damianesteban 很高兴能帮忙 :) - Hamish

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接