存储具有泛型

时间:2017-11-20 12:39:41

标签: ios swift

我正在为具有func结构的结构编写测试,该结构的子参数为Result<ModelProtocol>。 但是,当我为我的struct编写模拟时,它拒绝存储关于<T> != <ModelProtocol>的闭包。反过来说这是正确的,因为这是一种通用类型。

我现在得到的错误是:

Playground execution failed:

error: Test.playground:51:49: error: cannot assign value of type '(Result<T>) -> Void' to type '((Result<ModelProtocol>) -> Void)?'
            self.doSomethingCompletionHandler = completionHandler
                                                ^~~~~~~~~~~~~~~~~

问题是什么,因为<T>实际上属于T: ModelProtocol类型。 我如何存储闭包(completionHandler),以便稍后调用它来手动运行闭包(通过测试)。

这是我在操场上遇到的问题的一个例子:

public enum Result<Value> {

    case success(Value)
    case failure(Error)

    public var value: Value? {
        switch self {
        case .success(let value):
            return value
        case .failure:
            return nil
        }
    }

    public var error: Error? {
        switch self {
        case .success:
            return nil
        case .failure(let error):
            return error
        }
    }
}

protocol ModelProtocol {
    init(aString: String)
}

protocol AnImportantProtocol {
    func doSomething<T: ModelProtocol>(firstParameter: String, completionHandler: @escaping((Result<T>)->Void))
}

enum StructureError : Error {
    case defaultError
}

struct StructureOne : AnImportantProtocol {

    func doSomething<T: ModelProtocol>(firstParameter: String, completionHandler: @escaping ((Result<T>) -> Void)) {
        debugPrint("Doing something")

        if let model = ExampleModel(aString: "Test") as? T {
            completionHandler(.success(model))
        } else {
            completionHandler(.failure(StructureError.defaultError))
        }
    }
}

class StructureOneMock : AnImportantProtocol {

    var doSomethingInvokeCount: Int = 0
    var doSomethingCompletionHandler: ((Result<ModelProtocol>)->Void)? = nil

    func doSomething<T: ModelProtocol>(firstParameter: String, completionHandler: @escaping ((Result<T>) -> Void)) {
        self.doSomethingInvokeCount += 1
        self.doSomethingCompletionHandler = completionHandler
    }

    func callCompletionHandler(result: Result<ModelProtocol>) {
        if let doSomethingCompletionHandler = self.doSomethingCompletionHandler {
            doSomethingCompletionHandler(result)
        }
    }
}

struct ExampleModel {
    let someString: String
}

extension ExampleModel : ModelProtocol {
    init(aString: String) {
        self.someString = aString
    }
}

2 个答案:

答案 0 :(得分:1)

在这种情况下,最好使用associatedType约束,例如:

protocol AnImportantProtocol {
    associatedtype MyType: ModelProtocol
    func doSomething(firstParameter: String, completionHandler ((Result<MyType>)->Void)?)
}

然后在实现中输入typealias:

class StructureOneMock<T: ModelProtocol> : AnImportantProtocol {

typealias MyType = T

var doSomethingInvokeCount: Int = 0
var doSomethingCompletionHandler: ((Result<MyType>)->Void)? = nil

func doSomething(firstParameter: String, completionHandler: ((Result<MyType>) -> Void)?) {
    self.doSomethingInvokeCount += 1
    self.doSomethingCompletionHandler = completionHandler
}

func callCompletionHandler(result: Result<MyType>) {
    if let doSomethingCompletionHandler = self.doSomethingCompletionHandler {
        doSomethingCompletionHandler(result)
    }
}
}

您可以跳过实现中的泛型T并在那里指定具体类型。

答案 1 :(得分:1)

我相信你有一个不明显的逆变误差,因为方差发生在泛型参数中。

请考虑以下代码:

class Cat {}
class Kitten: Cat {}
class Cougar: Cat {}

protocol CatDayCareProtocol {
    func setRaiseFunction(raiseFunction: @escaping (Cat) -> Cougar)
}

class CatDayCare: CatDayCareProtocol {
    func setRaiseFunction(raiseFunction: @escaping (Cat) -> Cougar) {
        self.raiseFunction = raiseFunction
    }
    private var raiseFunction: ((Cat) -> Cougar)? = nil

    func callCougerRaiseFunction() -> Cougar? {
        let cougar = Cougar()
        return raiseFunction?(cougar)
    }
}

let catDayCare = CatDayCare()

catDayCare.setRaiseFunction(raiseFunction: { kitty: Kitten in
    return Cougar()
})

在此示例中,swift引发以下错误:

error: Contravariance.playground:23:51: error: expected expression
catDayCare.setRaiseFunction(raiseFunction: { kitty: Kitten in

这似乎不直观,因为小猫是猫,所以这不是很好吗?让我们考虑如果您尝试执行callCougerRaiseFunction()会发生什么。它实例化一只美洲狮,它是一只猫,并调用它的加强功能,它需要一只猫,所以这是合法的。但是,如果你传递一个期望小猫作为参数的功能,突然你将美洲狮传递给想要小猫的功能,这很难过。

现在举个例子,你有

func doSomething<T: ModelProtocol>(firstParameter: String, completionHandler: @escaping ((Result<T>) -> Void)) {
    self.doSomethingInvokeCount += 1
    self.doSomethingCompletionHandler = completionHandler
}

在这个例子中,T严格地与ModelProtocol一样或更具体(因为它可能是从ModelProtocol继承的任何协议),我认为这使得Result<T>作为函数参数与数据类型逆变{ {1}}。只是编译器不够聪明才能知道它是逆变,但它确实知道转换是不合法的。

至于实际解决问题,是否真的有必要使用通用?为什么你不能使用:

Result<ModelProtocol>