簡體   English   中英

如何將類類型作為函數參數傳遞

[英]How to pass a class type as a function parameter

我有一個通用函數,它調用 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 /*...*/

}

編輯:

正如holex所提到的,我嘗試使用object_getClass ,但現在我得到了:

錯誤:“類型‘CityInfo.Type’不符合協議‘AnyObject’”

需要做什么才能符合協議?

class CityInfo : NSObject {

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

您以錯誤的方式處理它:在 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 ,您可以為編譯器提供一種確定泛型參數的方法。

您可以通過這種方式獲取AnyObject的類:

斯威夫特 3.x

let myClass: AnyClass = type(of: self)

斯威夫特 2.x

let myClass: AnyClass = object_getClass(self)

如果您願意,您可以稍后將其作為參數傳遞。

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

}

斯威夫特 5

不完全相同的情況,但我遇到了類似的問題。 最終幫助我的是:

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

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

        default: break
    }
}

然后你可以像這樣在所述類的實例中調用它:

myFunction(type(of: self))

希望這對我同樣情況的人有所幫助。

只需將此處的每個代碼復制粘貼到 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 但你可以搜索你的更喜歡

使用obj-getclass

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

}

假設 self 是一個城市信息對象。

我最近遇到了這種情況,正在尋找一種方法來使我的 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
    }
    
}

不要忘記使用邊界而不是框架,因為在調用之前轉換了點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM