【问题标题】:How to pass a class type as a function parameter如何将类类型作为函数参数传递
【发布时间】:2014-06-19 14:24:21
【问题描述】:

我有一个通用函数,它调用 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 作为返回类值传递,但我得到一个编译错误“无法将表达式的类型 '()' 转换为类型 '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?
}

【问题讨论】:

  • 我认为 Swift 的泛型不像 java 那样工作。因此推断者不可能那么聪明。 id 省略 Class 事物并明确指定通用类型 CastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
  • 我遇到过你的情况,每个人都写过同样的事情——这是不可能的,但是如果你把这里写的所有答案都写下来,你就可以做到这一点,看看我的回答:stackoverflow.com/a/68930681/530884

标签: swift generics type-inference


【解决方案1】:

您以错误的方式处理它:在 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 的通用约束从&lt;T&gt; 更改为&lt;T: Initable&gt;

提示

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

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

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

}

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

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

【讨论】:

  • 感谢 T.Type 提示 - 正是我将类类型作为参数传递所需要的。
  • 不使用 模板函数,也可以将返回类作为 Initiable.Type 传递
  • 在原始代码中会产生不同的结果。泛型参数T 用于表达returningClass 与传递给completionHandler 的对象之间的关系。如果使用Initiable.Type,则此关系丢失。
  • 与?泛型不允许写func somefunc&lt;U&gt;()
  • 加戈,你指的是什么?
【解决方案2】:

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

斯威夫特 3.x

let myClass: AnyClass = type(of: self)

斯威夫特 2.x

let myClass: AnyClass = object_getClass(self)

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

【讨论】:

  • 你也可以self.dynamicType
  • 每当我尝试使用 myClass 时,都会导致“使用未声明的类型“myClass”的错误。Swift 3,Xcode 和 iOS 的最新版本
  • @Confused,我认为没有这样的问题,你可能需要给我更多关于上下文的信息。
  • 我正在制作一个按钮。在那个按钮的代码中(它是 SpriteKit,所以在 touchesBegan 里面)我希望按钮调用一个类函数,所以需要一个对该类的引用。所以在按钮类中,我创建了一个变量来保存对该类的引用。但是我不能让它持有一个类,或者那个类的类型,不管正确的措辞/术语是什么。我只能让它存储对实例的引用。最好,我想将对 Class Type 的引用传递到按钮中。但我刚开始创建内部引用,甚至无法正常工作。
  • 这是我在这里使用的方法和想法的延续:stackoverflow.com/questions/41092440/…。从那以后,我得到了一些使用协议的逻辑,但很快就遇到了这样一个事实,我还需要找出关联的类型和泛型来获得我认为我可以用更简单的机制做的事情。或者只是复制并粘贴大量代码。这是我通常做的;)
【解决方案3】:

我在 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(类型和元类型)之间区别的关键。
【解决方案4】:

只需将此处的每个代码复制粘贴到 swift 文件中即可:

#另存为: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()
    }
}

#另存为: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)")
})

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

【讨论】:

    【解决方案5】:

    斯威夫特 5

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

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

    然后你可以像这样在所述类的实例中调用它:

    myFunction(type(of: self))
    

    希望这对我遇到同样情况的人有所帮助。

    【讨论】:

      【解决方案6】:

      使用obj-getclass:

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

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

      【讨论】:

        【解决方案7】:

        我最近遇到了这种情况,正在寻找一种方法来使我的 UINavigationController 对除子视图按钮之外的所有内容都不可见。我把它放在一个自定义导航控制器中:

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

        不要忘记使用边界而不是框架,因为在调用之前转换点。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-01-04
          • 1970-01-01
          • 2022-10-06
          • 1970-01-01
          • 1970-01-01
          • 2019-03-05
          • 1970-01-01
          相关资源
          最近更新 更多