Як передавати тип класу як параметр функції


146

У мене є загальна функція, яка викликає веб-службу і серіалізує відповідь 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як значення returnClass, але я отримую помилку компіляції "Неможливо перетворити тип виразу '()" у тип "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?
}

Я не думаю, що дженерики Swifts працюють як javas. таким чином, підсумок не може бути таким розумним. id опустити клас <T> річ і чітко вказати Generic TypeCastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
Крістіан Дітріх

Відповіді:


144

Ви підходите до нього неправильно: у 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ви даєте компілятору спосіб визначення загального параметра.


Дякую за підказку T.Type - саме те, що мені було потрібно для передачі типу класу в якості аргументу.
Ешелон

"Порада" наприкінці врятувала мене
Олексій

Замість використання шаблону <T: Initiable> також можна передавати returnClass як Initiable.Тип
Devou

У вихідному коді, який дав би різні результати. Загальний параметр Tвикористовується для вираження співвідношення між returningClassоб'єктом і переданим об'єктом completionHandler. Якщо Initiable.Typeвикористовується, ці стосунки втрачаються.
EliaCereda

І? Дженріки не дозволяють писатиfunc somefunc<U>()
Гарго

34

Ви можете отримати клас за AnyObjectдопомогою цього способу:

Швидкий 3.x

let myClass: AnyClass = type(of: self)

Швидкий 2.x

let myClass: AnyClass = object_getClass(self)

і ви можете передати його як параметр пізніше, якщо хочете.


1
ви також можете зробитиself.dynamicType
newacct

1
Кожен раз, коли я намагаюся використовувати myClass, це викликає помилку "використання незадекларованого типу" myClass ". Swift 3, останні версії Xcode та iOS
Confused

@Confused, я не бачу такої проблеми з цим, можливо, вам потрібно буде дати мені більше інформації про контекст.
holex

Я роблю кнопку. У коді цієї кнопки (це SpriteKit, так що всередині touchchesBegan) я хочу, щоб кнопка викликала функцію Class, тому мені потрібна посилання на цей клас. Отже, у клавіші Button я створив змінну, щоб містити посилання на клас. Але я не можу змусити його мати клас чи тип, що є цим класом, незалежно від правильної редакції / термінології. Я можу отримати його лише для зберігання посилань на екземпляри. Переважно, я хотів би передати посилання посилання на Тип класу. Але я тільки почав із створення внутрішньої довідки, і навіть не міг так працювати.
Плутати

Це було продовженням методології та мислення, якими я користувався тут: stackoverflow.com/questions/41092440/… . З тих пір я отримав деяку логіку, працюючу з протоколами, але швидко зустрічаюся з тим, що мені також знадобиться з'ясувати пов'язані типи та генеричні дані, щоб отримати те, що я вважав, що можу зробити з більш простими механізмами. Або просто скопіюйте і вставте багато коду навколо. Що я зазвичай роблю;)
Плутати

7

У мене є подібний випадок використання в 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
    }

}

Я намагаюся зробити що - щось подібне, але я отримую повідомлення про помилку в callsite: Static method … requires that 'MyDecodable.Type' conform to 'Decodable'. Чи не проти оновити свою відповідь, щоб надати приклад дзвінка loadItem?
Баррі Джонс

Виявляється, це саме той момент, коли я повинен дізнатися різницю між .Typeта .self(тип і метатип).
Баррі Джонс

0

Використання obj-getclass:

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

}

Припустимо, що "себе" є об'єктом інформації про місто.


0

Швидкий 5

Не зовсім та сама ситуація, але у мене були подібні проблеми. Нарешті мені це допомогло:

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

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

        default: break
    }
}

Тоді ви можете викликати його всередині екземпляра згаданого класу, як це:

myFunction(type(of: self))

Сподіваюся, це допоможе комусь у моїй самій ситуації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.