[英]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
的類:
let myClass: AnyClass = type(of: self)
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
}
}
不完全相同的情況,但我遇到了類似的問題。 最終幫助我的是:
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.