Как передать тип класса в качестве параметра функции

У меня есть общая функция, которая вызывает веб-сервис и сериализует ответ 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 указывать AnyClass в качестве типа параметра?
  • При вызове метода я MyObject.self в качестве значения 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?
}

Ответ 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 от <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, вы даете компилятору способ определить общий параметр.

Ответ 2

вы можете получить класс AnyObject следующим образом:

Swift 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

и вы можете передать его как paramater позже, если хотите.

Ответ 3

Используйте obj-getclass:

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

}

Предполагая, что я - объект информации города.

Ответ 4

У меня есть похожий случай использования в 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
    }

}