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

时间:2023-01-23 18:06:33

I have a generic class for making and processing JSON API requests. I pass in the TParam and TResult template parameters but when I use a derived type it's implementation is not being called.

我有一个通用类,用于创建和处理JSON API请求。我传入TParam和TResult模板参数,但是当我使用派生类型时,它的实现不会被调用。

Here is some code you can throw in a playground to illustrate:

这里有一些你可以在操场上展示的代码:

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

The really weird thing is that only the DerivedAPIResult.parseData() is not called, whereas the DerivedAPIParams.getData() method is called. Any ideas why?

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

3 个解决方案

#1


2  

UPDATE: This defect is fixed with XCode 6.3 beta1 (Apple Swift version 1.2 (swiftlang-602.0.37.3 clang-602.0.37))

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

Added info for a workaround when using XCode 6.1 (Swift 1.1) See these dev forum threads for details: https://devforums.apple.com/thread/251920?tstart=30 https://devforums.apple.com/message/1058033#1058033

在使用XCode 6.1 (Swift 1.1)时为解决方案添加了一些信息,请参阅这些开发论坛线程的详细信息:https://devforums.apple.com/thread/251920?tstart = 30 https://devforums.apple.com/message/1058033 1058033

In a very similar code sample I was having the exact same issue. After waiting through beta after beta for a "fix", I did more digging and discovered that I can get the expect results by making the base class init() required.

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

By way of example, here is Matt Gibson's reduced example "fixed" by adding the proper init() to ApiResult

举个例子,这里是Matt Gibson的简化示例“修复”,通过向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>()

I do not know why this works. If I get time I will dig deeper, but my best guess is that for some reason having required init is causing different object allocation/construction code to be generated that forces proper set up of the vtable we are hoping for.

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

#2


1  

Looks possibly surprising, certainly. I've reduced your case to something rather simpler, which might help to figure out what's going on:

看起来可能令人惊讶,当然。我把你的情况简化成更简单的事情,这可能有助于弄清楚到底是怎么回事:

// 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>()

...so it seems that creating a new instance of a templated class with a type constraint gives you an instance of the constraint class, rather than the derived class you use to instantiate the specific template instance.

…因此,似乎使用类型约束创建模板类的新实例会为您提供约束类的实例,而不是用于实例化特定模板实例的派生类。

Now, I'd say that the generics in Swift, looking through the Swift book, would probably prefer you not to create your own instances of derived template constraint classes within the template code, but instead just define places to hold instances that are then passed in. By which I mean that this works:

现在,我要说的是,Swift中的泛型,通过查看Swift book,可能更希望您不要在模板代码中创建派生模板约束类的实例,而是定义存放然后传入的实例的位置。我的意思是,这行得通:

// 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())

...but I'm not clear whether what you're trying should technically be allowed or not.

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

My guess is that the way generics are implemented means that there's not enough type information when creating the template to create objects of the derived type from "nothing" within the template—so you'd have to create them in your code, which knows about the derived type it wants to use, and pass them in, to be held by templated constrained types.

我的猜测是,泛型的实现方式意味着没有足够的类型信息创建模板来创建对象时派生类型的“无”在template-so你必须在代码中创建它们,它知道它要使用的派生类型,并通过他们,由模板化约束类型。

#3


-1  

parseData needs to be defined as a class func which creates an instance of itself, assigns whatever instance properties, and then returns that instance. Basically, it needs to be a factory method. Calling .self() on the type is just accessing the type as a value, not an instance. I'm surprised you don't get some kind of error calling an instance method on a type.

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

#1


2  

UPDATE: This defect is fixed with XCode 6.3 beta1 (Apple Swift version 1.2 (swiftlang-602.0.37.3 clang-602.0.37))

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

Added info for a workaround when using XCode 6.1 (Swift 1.1) See these dev forum threads for details: https://devforums.apple.com/thread/251920?tstart=30 https://devforums.apple.com/message/1058033#1058033

在使用XCode 6.1 (Swift 1.1)时为解决方案添加了一些信息,请参阅这些开发论坛线程的详细信息:https://devforums.apple.com/thread/251920?tstart = 30 https://devforums.apple.com/message/1058033 1058033

In a very similar code sample I was having the exact same issue. After waiting through beta after beta for a "fix", I did more digging and discovered that I can get the expect results by making the base class init() required.

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

By way of example, here is Matt Gibson's reduced example "fixed" by adding the proper init() to ApiResult

举个例子,这里是Matt Gibson的简化示例“修复”,通过向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>()

I do not know why this works. If I get time I will dig deeper, but my best guess is that for some reason having required init is causing different object allocation/construction code to be generated that forces proper set up of the vtable we are hoping for.

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

#2


1  

Looks possibly surprising, certainly. I've reduced your case to something rather simpler, which might help to figure out what's going on:

看起来可能令人惊讶,当然。我把你的情况简化成更简单的事情,这可能有助于弄清楚到底是怎么回事:

// 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>()

...so it seems that creating a new instance of a templated class with a type constraint gives you an instance of the constraint class, rather than the derived class you use to instantiate the specific template instance.

…因此,似乎使用类型约束创建模板类的新实例会为您提供约束类的实例,而不是用于实例化特定模板实例的派生类。

Now, I'd say that the generics in Swift, looking through the Swift book, would probably prefer you not to create your own instances of derived template constraint classes within the template code, but instead just define places to hold instances that are then passed in. By which I mean that this works:

现在,我要说的是,Swift中的泛型,通过查看Swift book,可能更希望您不要在模板代码中创建派生模板约束类的实例,而是定义存放然后传入的实例的位置。我的意思是,这行得通:

// 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())

...but I'm not clear whether what you're trying should technically be allowed or not.

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

My guess is that the way generics are implemented means that there's not enough type information when creating the template to create objects of the derived type from "nothing" within the template—so you'd have to create them in your code, which knows about the derived type it wants to use, and pass them in, to be held by templated constrained types.

我的猜测是,泛型的实现方式意味着没有足够的类型信息创建模板来创建对象时派生类型的“无”在template-so你必须在代码中创建它们,它知道它要使用的派生类型,并通过他们,由模板化约束类型。

#3


-1  

parseData needs to be defined as a class func which creates an instance of itself, assigns whatever instance properties, and then returns that instance. Basically, it needs to be a factory method. Calling .self() on the type is just accessing the type as a value, not an instance. I'm surprised you don't get some kind of error calling an instance method on a type.

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