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

我有一个通用函数,它调用 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作为返回的 Class 值,但是我得到了一个编译的 错误“无法将表达式的类型’()’转换为类型‘ 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?
}
117824 次浏览

你可以通过这种方式得到 AnyObject的类:

斯威夫特3. x

let myClass: AnyClass = type(of: self)

Swift 2. x

let myClass: AnyClass = object_getClass(self)

如果愿意,可以稍后将其作为参数传递。

使用 obj-getclass:

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


}

假设 self 是一个城市信息对象。

你正在以错误的方式接近它: 在 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>即可。

小费

如果出现奇怪的错误,比如“无法将表达式的 type’()’转换为 type‘ 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,可以为编译器提供一种确定泛型参数的方法。

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

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
}


}


Swift 5

情况并不完全一样,但我也有类似的问题。最终帮助我的是:

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


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


default: break
}
}

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

myFunction(type(of: self))

希望这能帮到和我处境相同的人。

我最近遇到了这个问题,想找到一种方法,让我的 UINavigationController 对除了 subview 按钮之外的所有东西都不可见。我把这个放在一个定制的导航控制器里:

// MARK:- UINavigationBar Override
private extension UINavigationBar {
    

override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// Make the navigation bar ignore interactions unless with a subview button
return self.point(inside: point, with: event, type: UIButton.self)
}
    

}


// MARK:- Button finding hit test
private extension UIView {
    

func point<T: UIView>(inside point: CGPoint, with event: UIEvent?, type: T.Type) -> Bool {
        

guard self.bounds.contains(point) else { return false }
        

if subviews.contains(where: { $0.point(inside: convert(point, to: $0), with: event, type: type) }) {
return true
}


return self is T
}
    

}

不要忘记使用边界代替帧,因为点在调用之前已经转换。

只需将每个代码复制粘贴到快速文件中:

# 另存为: APICaller.swift

import Foundation


struct APICaller
{
public static func get<T: Decodable>(url: String, receiveModel: T.Type, completion:@escaping (Decodable) -> ())
{
send(url: url, json: nil, receiveModel: receiveModel, completion: completion, httpMethod: "GET")
}
    

public static func post<T: Decodable>(url: String, json: [String: Any]?, receiveModel: T.Type, completion:@escaping (Decodable) -> ())
{
send(url: url, json: nil, receiveModel: receiveModel, completion: completion, httpMethod: "POST")
}
    

public static func delete<T: Decodable>(url: String, json: [String: Any]?, receiveModel: T.Type, completion:@escaping (Decodable) -> ())
{
send(url: url, json: nil, receiveModel: receiveModel, completion: completion, httpMethod: "DELETE")
}


private static func send<T: Decodable>(url: String, json: [String: Any]?, receiveModel: T.Type, completion:@escaping (Decodable) -> (), httpMethod: String)
{
// create post request
let urlURL: URL = URL(string: url)!
var httpRequest: URLRequest = URLRequest(url: urlURL)
httpRequest.httpMethod = httpMethod
        

if(json != nil)
{
// serialize map of strings to json object
let jsonData: Data = try! JSONSerialization.data(withJSONObject: json!)
// insert json data to the request
httpRequest.httpBody = jsonData
httpRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
}
        

// create an asynchronus task to post the request
let task = URLSession.shared.dataTask(with: httpRequest)
{ jsonData, response, error in
// on callback parse the json into the receiving model object
let receivedModelFilled: Decodable = Bundle.main.decode(receiveModel, from: jsonData!)


// cal the user callback with the constructed object from json
DispatchQueue.main.async {
completion(receivedModelFilled)
}
}
task.resume()
}
}

# save as: TestService. swift

import Foundation


struct TestService: Codable
{
let test: String
}

然后你可以这样使用它:

let urlString: String = "http://localhost/testService"  <--- replace with your actual service url


// call the API in post request
APICaller.post(url: urlString, json: ["test": "test"], receiveModel: TestService.self, completion: { testReponse in
// when response is received - do something with it in this callback
let testService: TestService = testReponse as! TestService
print("testService: \(testService)")
})

提示: 我使用在线服务将我的 JSONs 转换为快速文件,所以我只剩下写调用和处理响应 我使用这个: https://app.quicktype.io,但是你可以搜索你喜欢的那个