Como passar um tipo de classe como parâmetro de função

146

Eu tenho uma função genérica que chama um serviço web e serializa a resposta JSON de volta para um objeto.

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 */
}

O que estou tentando realizar é o equivalente a este código Java

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • Minha assinatura de método para o que estou tentando realizar está correta?
  • Mais especificamente, especificar AnyClasscomo tipo de parâmetro a coisa certa a fazer?
  • Ao chamar o método, estou passando MyObject.selfcomo o valor returnClass, mas recebo um erro de compilação "Não é possível converter o tipo da expressão '()' para o tipo 'String'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

Editar:

Tentei usar object_getClass, como mencionado pela holex, mas agora recebo:

erro: "O tipo 'CityInfo.Type' não está em conformidade com o protocolo 'AnyObject'"

O que precisa ser feito para estar em conformidade com o protocolo?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}
Jean-Francois Gagnon
fonte
Eu não acho que os genéricos Swifts funcionem como javas. assim, o inferidor não pode ser tão inteligente. ID de omitir o Class <T> Coisa e especificar o tipo genérico explicitamenteCastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
Christian Dietrich

Respostas:

144

Você está abordando isso da maneira errada: no Swift, ao contrário do Objective-C, as classes têm tipos específicos e até uma hierarquia de herança (ou seja, se a classe é Bherdada de A, então B.Typetambém é herdada de 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

É por isso que você não deve usar AnyClass, a menos que queira realmente permitir qualquer aula. Nesse caso, o tipo correto seria T.Type, porque expressa o link entre o returningClassparâmetro e o parâmetro do fechamento.

De fato, usá-lo em vez de AnyClasspermitir que o compilador deduza corretamente os tipos na chamada do método:

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())
}

Agora existe o problema de construir uma instância Tpara a qual passar handler: se você tentar executar o código agora, o compilador reclamará que Tnão é construtível (). E com razão: Tdeve ser explicitamente restringido para exigir que implemente um inicializador específico.

Isso pode ser feito com um protocolo como o seguinte:

protocol Initable {
    init()
}

class CityInfo : NSObject, Initable {
    var cityName: String?
    var regionCode: String?
    var regionName: String?

    // Nothing to change here, CityInfo already implements init()
}

Então você só precisa alterar as restrições genéricas de invokeServicede <T>para <T: Initable>.

Dica

Se você receber erros estranhos como "Não é possível converter o tipo da expressão '()' para o tipo 'String'", geralmente é útil mover todos os argumentos da chamada de método para sua própria variável. Ajuda a restringir o código que está causando o erro e a descobrir problemas de inferência de tipo:

let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self

CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/

}

Agora, existem duas possibilidades: o erro é movido para uma das variáveis ​​(o que significa que a parte errada está lá) ou você recebe uma mensagem enigmática como "Não é possível converter o tipo da expressão ()em tipo ($T6) -> ($T6) -> $T5".

A causa do último erro é que o compilador não pode inferir os tipos do que você escreveu. Nesse caso, o problema é que isso Té usado apenas no parâmetro do fechamento e o fechamento que você passou não indica nenhum tipo específico, portanto o compilador não sabe que tipo inferir. Alterando o tipo de returningClassinclusão, Tvocê fornece ao compilador uma maneira de determinar o parâmetro genérico.

EliaCereda
fonte
Obrigado pela dica T.Type - exatamente o que eu precisava para passar um tipo de classe como argumento.
Echelon
A "dica" no final me salvou
Alex
Em vez de usar um <T: Initiable> função templated, também é possível passar o returningClass como um Initiable.Type
Devous
No código original, isso teria produzido resultados diferentes. O parâmetro genérico Té usado para expressar a relação entre o returningClasse o objeto passado para completionHandler. Se Initiable.Typefor usado, esse relacionamento será perdido.
EliaCereda 20/0318
E? Genéricos não permitem a escreverfunc somefunc<U>()
Gargo
34

você pode obter a aula AnyObjectdessa maneira:

Swift 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

e você pode passá-lo como paramater mais tarde, se desejar.

holex
fonte
1
você também pode fazerself.dynamicType
newacct
1
Sempre que tento usar o myClass, está causando um erro de "uso do tipo não declarado" myClass ". Swift 3, versões mais recentes do Xcode e iOS
Confused
@ Confuso, não vejo esse problema com isso, talvez você precise me fornecer mais informações sobre o contexto.
holex 12/12
Eu estou fazendo um botão. No código desse botão (é SpriteKit, portanto, dentro de touchesBegan), quero que o botão chame uma função Class, portanto, preciso de uma referência a essa classe. Portanto, na classe Button, criei uma variável para manter uma referência à classe. Mas não consigo que ele mantenha uma classe ou o tipo que é essa classe, seja qual for a redação / terminologia correta. Só posso fazer com que ele armazene referências a instâncias. De preferência, gostaria de passar uma referência ao tipo de classe para o botão. Mas comecei a criar uma referência interna e nem consegui fazer isso funcionar.
Confuso
Foi uma continuação da metodologia e do pensamento que eu estava usando aqui: stackoverflow.com/questions/41092440/… . Desde então, consegui alguma lógica trabalhando com protocolos, mas rapidamente me deparo com o fato de que também precisarei descobrir tipos e genéricos associados para obter o que pensei que poderia fazer com mecanismos mais simples. Ou simplesmente copie e cole um monte de código. Que é o que eu faço normalmente;)
Confuso
7

Eu tenho um caso de uso semelhante no 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
fonte
Eu estou tentando fazer algo semelhante, mas eu recebo um erro no callsite: Static method … requires that 'MyDecodable.Type' conform to 'Decodable'. Você se importaria de atualizar sua resposta para dar um exemplo de chamada loadItem?
Barry Jones
Acontece que este é o ponto em que tenho que aprender a diferença entre .Typee .self(tipo e metatipo).
Barry Jones
0

Use obj-getclass:

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

}

Supondo que o self é um objeto de informações da cidade.

Desfazer
fonte
0

Swift 5

Não é exatamente a mesma situação, mas eu estava tendo um problema semelhante. O que finalmente me ajudou foi isso:

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

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

        default: break
    }
}

Então você pode chamá-lo dentro de uma instância da referida classe como esta:

myFunction(type(of: self))

Espero que isso ajude alguém na minha mesma situação.

Merricat
fonte