2

我有一个用于制作和处理 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()方法。任何想法为什么?

4

3 回答 3

2

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

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

在一个非常相似的代码示例中,我遇到了完全相同的问题。在 beta 之后等待 beta 进行“修复”之后,我做了更多的挖掘,发现我可以通过使基类 init() 成为必需的来获得预期的结果。

例如,这是 Matt Gibson 的简化示例,通过将适当的 init() 添加到 ApiResult 来“修复”

// 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。

于 2014-08-19T16:47:37.873 回答
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())

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

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

于 2014-06-13T05:45:37.227 回答
-1

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

于 2014-06-13T14:39:33.327 回答