如何将类类型作为函数参数传递

Jea*_*non 136 generics type-inference swift

我有一个泛型函数调用Web服务并将JSON响应序列化回一个对象.

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: AnyClass, completionHandler handler: ((T) -> ())) {

            /* Construct the URL, call the service and parse the response */
}
Run Code Online (Sandbox Code Playgroud)

我想要完成的是相当于这个Java代码

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
Run Code Online (Sandbox Code Playgroud)
  • 我的方法签名是我正在努力完成的吗?
  • 更具体地说,指定AnyClass作为参数类型正确的事情?
  • 在调用方法时,我将MyObject.self作为returnsClass值传递,但是我收到编译错误"无法将表达式的类型'()'转换为'String'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}
Run Code Online (Sandbox Code Playgroud)

编辑:

我尝试使用object_getClass,如holex所提到的,但现在我得到:

错误:"类型'CityInfo.Type'不符合协议'AnyObject'"

需要做什么才能符合协议?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}
Run Code Online (Sandbox Code Playgroud)

Eli*_*eda 135

您正在以错误的方式接近它:在Swift中,与Objective-C不同,类具有特定类型,甚至具有继承层次结构(即,如果类B继承A,则B.Type也继承自A.Type):

class A {}
class B: A {}
class C {}

// B inherits from A
let object: A = B()

// B.Type also inherits from A.Type
let type: A.Type = B.self

// Error: 'C' is not a subtype of 'A'
let type2: A.Type = C.self
Run Code Online (Sandbox Code Playgroud)

这就是为什么你不应该使用AnyClass,除非你真的想允许任何课程.在这种情况下,正确的类型将是T.Type,因为它表示returningClass参数和闭包参数之间的链接.

实际上,使用它代替AnyClass允许编译器正确推断方法调用中的类型:

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: T.Type, completionHandler handler: ((T) -> ())) {
    // The compiler correctly infers that T is the class of the instances of returningClass
    handler(returningClass())
}
Run Code Online (Sandbox Code Playgroud)

现在有一个构造T传递给的实例的问题handler:如果你现在尝试运行代码,编译器会抱怨它T是不可构造的().理所当然地:T必须明确约束,要求它实现特定的初始化程序.

这可以通过以下协议来完成:

protocol Initable {
    init()
}

class CityInfo : NSObject, Initable {
    var cityName: String?
    var regionCode: String?
    var regionName: String?

    // Nothing to change here, CityInfo already implements init()
}
Run Code Online (Sandbox Code Playgroud)

那么你只需要改变的泛型约束invokeService<T><T: Initable>.

小费

如果您遇到奇怪的错误,例如"无法将表达式的类型'()'转换为'String'"类型,则将方法调用的每个参数移动到其自己的变量通常很有用.它有助于缩小导致错误的代码并发现类型推断问题:

let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self

CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/

}
Run Code Online (Sandbox Code Playgroud)

现在有两种可能性:错误移动到其中一个变量(这意味着错误的部分存在)或者你得到一个神秘的消息,如"无法将表达式的类型转换()为类型($T6) -> ($T6) -> $T5".

后一个错误的原因是编译器无法推断您编写的类型.在这种情况下,问题是T仅在闭包的参数中使用,并且您传递的闭包不指示任何特定类型,因此编译器不知道要推断的类型.通过更改returningClass要包含的类型,T您可以为编译器提供一种确定泛型参数的方法.


hol*_*lex 34

你可以AnyObject通过这种方式获得课程:

Swift 3.x

let myClass: AnyClass = type(of: self)
Run Code Online (Sandbox Code Playgroud)

Swift 2.x

let myClass: AnyClass = object_getClass(self)
Run Code Online (Sandbox Code Playgroud)

如果你愿意,你可以稍后将它作为参数传递给你.


小智 9

我在 swift5 中有一个类似的用例:

class PlistUtils {

    static let shared = PlistUtils()

    // write data
    func saveItem<T: Encodable>(url: URL, value: T) -> Bool{
        let encoder = PropertyListEncoder()
        do {
            let data = try encoder.encode(value)
            try data.write(to: url)
            return true
        }catch {
            print("encode error: \(error)")
            return false
        }
    }

    // read data

    func loadItem<T: Decodable>(url: URL, type: T.Type) -> Any?{
        if let data = try? Data(contentsOf: url) {
            let decoder = PropertyListDecoder()
            do {
                let result = try decoder.decode(type, from: data)
                return result
            }catch{
                print("items decode failed ")
                return nil
            }
        }
        return nil
    }

}

Run Code Online (Sandbox Code Playgroud)