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


146

我有一个通用函数,该函数调用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 */
}

我要完成的是等效于此Java代码

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • 我要完成的方法签名正确吗?
  • 更具体地说,AnyClass将参数类型指定为正确的做法是正确的吗?
  • 调用该方法时,我将其MyObject.self作为returningClass值传递,但是出现编译错误“无法将表达式的类型'()'转换为'String'类型”
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

编辑:

object_getClass如Holex所述,我尝试使用,但现在得到了:

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

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

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}

我不认为Swifts泛型像Java一样工作。因此,推断者不可能那么聪明。ID省略类<T>物与显式地指定所述类型CastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
基督教迪特里希

Answers:


144

您用错误的方式进行处理:在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

这就是为什么您不应该使用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())
}

现在存在构造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()
}

然后,您只需要更改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 /*...*/

}

现在有两种可能性:错误移至变量之一(这意味着存在错误的部分),或者您收到诸如“无法将表达式的类型转换()为类型($T6) -> ($T6) -> $T5” 之类的神秘信息。

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


感谢T.Type提示-正是我需要将类类型作为参数传递的内容。
梯队

最后的“提示”救了我
Alex

代替使用的<T:Initiable>模板函数,它也可以传递returningClass作为Initiable.Type
Devous

在原始代码中会产生不同的结果。通用参数T用于表示returningClass和传递给的对象之间的关系completionHandler。如果Initiable.Type使用,则此关系丢失。
EliaCereda

和?泛型不允许写作func somefunc<U>()
Gargo

34

您可以AnyObject通过以下方式获取类:

斯威夫特3.x

let myClass: AnyClass = type(of: self)

斯威夫特2.x

let myClass: AnyClass = object_getClass(self)

然后您可以根据需要将其作为参数传递。


1
您也可以这样做self.dynamicType
newacct 2014年

1
每当我尝试使用myClass时,都会导致“使用未声明的类型” myClass”的错误。Swift 3,Xcode和iOS的最新版本
困惑

@Confused,我认为没有这种问题,您可能需要给我更多有关上下文的信息。
Holex

我在按一个按钮。在该按钮的代码中(它是SpriteKit,所以在touchesBegan内部),我希望该按钮调用Class函数,因此需要对该类的引用。因此,在Button类中,我创建了一个变量来保存对该类的引用。但是,无论正确的措辞/术语是什么,我都无法容纳一个Class或那个Class的Type。我只能得到它来存储对实例的引用。最好是,我想将对Class Type的引用传递到按钮中。但是我只是从创建内部参考开始,甚至无法正常工作。
困惑的

这是我在这里使用的方法和思想的延续:stackoverflow.com/questions/41092440/…。从那以后,我已经有了一些与协议兼容的逻辑,但是很快就遇到了一个事实,那就是我还需要弄清楚关联的类型和泛型,以获得我认为可以用更简单的机制完成的工作。或者只是复制并粘贴很多代码。这通常是我通常要执行的操作;)
混淆了

7

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

}

我正在尝试做类似的事情,但在呼叫站点出现错误Static method … requires that 'MyDecodable.Type' conform to 'Decodable'。您介意更新答案以举一个例子loadItem吗?
巴里·琼斯

事实证明,这是我必须学习.Type.self(类型和元类型)之间的区别的地方。
巴里·琼斯

0

用途obj-getclass

CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: obj-getclass(self)) { cityInfo in /*...*/

}

假设self是城市信息对象。


0

斯威夫特5

情况不完全相同,但是我遇到了类似的问题。最终帮助我的是:

func myFunction(_ myType: AnyClass)
{
    switch type
    {
        case is MyCustomClass.Type:
            //...
            break

        case is MyCustomClassTwo.Type:
            //...
            break

        default: break
    }
}

然后,您可以在上述类的实例中调用它,如下所示:

myFunction(type(of: self))

希望这对我的情况有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.