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

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передаю значение returningClass, но получаю ошибку компиляции «Невозможно преобразовать тип выражения« () »в тип« Строка »
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> и явно указывает универсальный типCastDAO.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>.

Наконечник

Если вы получаете странные ошибки, такие как «Невозможно преобразовать тип выражения« () »в тип« Строка », часто бывает полезно переместить каждый аргумент вызова метода в его собственную переменную. Это помогает сузить код, вызывающий ошибку, и выявить проблемы с выводом типа:

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

EliaCereda
источник
Спасибо за подсказку T.Type - именно то, что мне нужно для передачи типа класса в качестве аргумента.
Эшелон
«Подсказка» в конце спасла меня
Алекс
Вместо использования шаблонной функции <T: Initiable>, также возможно передать returningClass как Initiable.Type
Devous
В оригинальном коде это дало бы разные результаты. Универсальный параметр Tиспользуется для выражения отношения между returningClassобъектом и объектом, переданным completionHandler. Если Initiable.Typeиспользуется, это отношение теряется.
EliaCereda
И? Дженерики не позволяют писатьfunc somefunc<U>()
Gargo
34

Вы можете получить класс AnyObjectчерез этот путь:

Swift 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

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

holex
источник
1
Вы также можете сделатьself.dynamicType
newacct
1
Всякий раз, когда я пытаюсь использовать myClass, это вызывает ошибку «использования необъявленного типа« myClass ». Swift 3, последние сборки Xcode и iOS
Confused
@ Confused, я не вижу такой проблемы с этим, возможно, вам придется дать мне больше информации о контексте.
Holex
Я делаю кнопку. В коде этой кнопки (это SpriteKit, поэтому внутри touchesBegan) я хочу, чтобы кнопка вызывала функцию класса, поэтому нужна ссылка на этот класс. Таким образом, в классе кнопок я создал переменную для хранения ссылки на класс. Но я не могу заставить его содержать класс или тип, который является этим классом, какой бы правильной формулировка / терминология ни была. Я могу получить его только для хранения ссылок на экземпляры. Желательно передать ссылку на тип класса в кнопку. Но я только начал с создания внутренней ссылки и даже не смог заставить ее работать.
Смущенный
Это было продолжение методологии и мысли, которую я использовал здесь: stackoverflow.com/questions/41092440/… . С тех пор я получил некоторую логику работы с протоколами, но быстро столкнусь с фактом, что мне также понадобится выяснить связанные типы и обобщения, чтобы получить то, что, как я думал, можно сделать с помощью более простых механизмов. Или просто скопируйте и вставьте много кода вокруг. Что я обычно делаю;)
Смущенный
8

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

}
Johney
источник
Я пытаюсь сделать что - то подобное, но я получаю сообщение об ошибке в 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

Swift 5

Не совсем та же ситуация, но у меня была похожая проблема. Что мне наконец помогло, так это:

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

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

        default: break
    }
}

Затем вы можете вызвать его внутри экземпляра указанного класса следующим образом:

myFunction(type(of: self))

Надеюсь, это поможет кому-то в моей ситуации.

Merricat
источник