未被调用的泛型类模板的派生类方法

时间:2014-06-12 20:02:56

标签: templates generics swift ios8 swift-playground

我有一个用于制作和处理JSON API请求的泛型类。我传递了TParamTResult模板参数,但是当我使用派生类型时,它的实现没有被调用。

以下是您可以在游乐场中举例说明的一些代码:

import Cocoa

// Base class for parameters to POST to service
class APIParams {
    func getData() -> Dictionary<String, AnyObject> {
        return Dictionary<String, AnyObject>()
    }
}

// Base class for parsing a JSON Response
class APIResult {
    func parseData(data: AnyObject?) {

    }
}

// Derived example for a login service
class DerivedAPIParams: APIParams {
    var user = "some@one.com"
    var pass = "secret"

    // THIS METHOD IS CALLED CORRECTLY
    override func getData() -> Dictionary<String, AnyObject> {
        return [ "user": user, "pass": pass ]
    }
}

// Derived example for parsing a login response
class DerivedAPIResult: APIResult {
    var success = false
    var token:String? = ""

    // THIS METHOD IS NEVER CALLED
    override func parseData(data: AnyObject?) {
        /*
        self.success = data!.valueForKey("success") as Bool
        self.token = data!.valueForKey("token") as? String
        */

        self.success = true
        self.token = "1234"
    }
}

class APIOperation<TParams: APIParams, TResult: APIResult> {
    var url = "http://localhost:3000"

    func request(params: TParams, done: (NSError?, TResult?) -> ()) {            
        let paramData = params.getData()

        // ... snip making a request to website ...

        let result = self.parseResult(nil)

        done(nil, result)
    }

    func parseResult(data: AnyObject?) -> TResult {
        var result = TResult.self()

        // This should call the derived implementation if passed, right?
        result.parseData(data)

        return result
    }
}

let derivedOp = APIOperation<DerivedAPIParams, DerivedAPIResult>()
let params = DerivedAPIParams()

derivedOp.request(params) {(error, result) in
    if result? {
        result!.success
    }
}

真正奇怪的是,只调用了DerivedAPIResult.parseData(),而调用了DerivedAPIParams.getData()方法。有什么想法吗?

3 个答案:

答案 0 :(得分:2)

更新:此缺陷已通过XCode 6.3 beta1修复(Apple Swift 1.2版(swiftlang-602.0.37.3 clang-602.0.37))

使用XCode 6.1(Swift 1.1)时添加了解决方法的信息 有关详细信息,请参阅这些d​​ev论坛帖 https://devforums.apple.com/thread/251920?tstart=30 https://devforums.apple.com/message/1058033#1058033

在一个非常相似的代码示例中,我遇到了完全相同的问题。在测试版之后等待测试版本的#34;修复&#34;后,我做了更多挖掘,发现我可以通过使基类init()成为必需的结果来获得期望结果。

举例来说,这是Matt Gibson的简化示例&#34; fixed&#34;通过向ApiResult添加适当的init()

// Base class for parsing a JSON Response
class APIResult {
    // adding required init() to base class yields the expected behavior
    required init() {}
}

// Derived example for parsing a login response
class DerivedAPIResult: APIResult {

}

class APIOperation<TResult: APIResult> {
    init() {            
        // EDIT: workaround for xcode 6.1, tricking the compiler to do what we want here
        let tResultClass : TResult.Type = TResult.self
        var test = tResultClass()
        // should be able to just do, but it is broken and acknowledged as such by Apple
        // var test = TResult() 

        println(test.self) // now shows that we get DerivedAPIResult
    }
}

// Templated creation creates APIResult
let derivedOp = APIOperation<DerivedAPIResult>()

我不知道为什么会这样。如果我有时间,我会深入挖掘,但我最好的猜测是,由于某种原因,需要init导致生成不同的对象分配/构造代码,迫使我们希望正确设置vtable。

答案 1 :(得分:1)

当然看起来可能令人惊讶。我已经将你的案例简化为更简单的事情,这可能有助于弄清楚发生了什么:

// Base class for parsing a JSON Response
class APIResult {
}

// Derived example for parsing a login response
class DerivedAPIResult: APIResult {
}

class APIOperation<TResult: APIResult> {
    init() {
        var test = TResult()
        println(test.self) // Shows that we get APIResult, not DerivedAPIResult
    }
}

// Templated creation creates APIResult
let derivedOp = APIOperation<DerivedAPIResult>()

...所以似乎创建一个带有类型约束的模板化类的新实例会为您提供约束类的实例,而不是您用来实例化特定模板实例的派生类。

现在,我要说Swift中的泛型,通过Swift书,可能更喜欢你不在模板代码中创建自己的派生模板约束类实例,而是只定义用于保存实例的实例然后传入。我的意思是这有效:

// Base class for parsing a JSON Response
class APIResult {
}

// Derived example for parsing a login response
class DerivedAPIResult: APIResult {
}

class APIOperation<T: APIResult> {
    var instance: T
    init(instance: T) {
        self.instance = instance
        println(instance.self) // As you'd expect, this is a DerivedAPIResult
    }
}

let derivedOpWithPassedInstance = APIOperation<DerivedAPIResult>(instance: DerivedAPIResult())

...但我不清楚你在尝试的技术是否应该被允许。

我的猜测是,实现泛型的方式意味着在创建模板时没有足够的类型信息来从模板中的“无”创建派生类型的对象 - 所以你必须在代码中创建它们,它知道它想要使用的派生类型,并将它们传递给模板化的约束类型。

答案 2 :(得分:-1)

parseData需要定义为class func,它创建自己的实例,分配任何实例属性,然后返回该实例。基本上,它需要是一种工厂方法。在类型上调用.self()只是将类型作为值访问,而不是实例。我很惊讶你在类型上调用实例方法时没有出现某种错误。