Ligação fraca - verifique se existe uma classe e use essa classe

87

Estou tentando criar um aplicativo universal para iPhone, mas ele usa uma classe definida apenas em uma versão mais recente do SDK. A estrutura existe em sistemas mais antigos, mas uma classe definida na estrutura não.

Eu sei que quero usar algum tipo de link fraco, mas qualquer documentação que eu possa encontrar fala sobre verificações de tempo de execução para a existência de funções - como faço para verificar se uma classe existe?

psicótico
fonte
Qual é a classe? Talvez existam outras soluções alternativas.
Marcelo Cantos

Respostas:

164

TLDR

Atual:

  • Swift :if #available(iOS 9, *)
  • Obj-C, iOS :if (@available(iOS 11.0, *))
  • Obj-C, OS X :if (NSClassFromString(@"UIAlertController"))

Legado:

  • Swift (versões anteriores a 2.0) :if objc_getClass("UIAlertController")
  • Obj-C, iOS (versões anteriores a 4.2) :if (NSClassFromString(@"UIAlertController"))
  • Obj-C, iOS (versões anteriores a 11.0) :if ([UIAlertController class])

Swift 2+

Embora historicamente seja recomendado verificar os recursos (ou existência de classe) em vez de versões específicas do sistema operacional, isso não funciona bem no Swift 2.0 por causa da introdução da verificação de disponibilidade .

Use desta forma:

if #available(iOS 9, *) {
    // You can use UIStackView here with no errors
    let stackView = UIStackView(...)
} else {
    // Attempting to use UIStackView here will cause a compiler error
    let tableView = UITableView(...)
}

Observação: se você tentar usar objc_getClass(), receberá o seguinte erro:

⛔️ 'UIAlertController' está disponível apenas no iOS 8.0 ou mais recente.


Versões anteriores de Swift

if objc_getClass("UIAlertController") != nil {
    let alert = UIAlertController(...)
} else {
    let alert = UIAlertView(...)
}

Observe que objc_getClass() é mais confiável do que NSClassFromString()ouobjc_lookUpClass() .


Objective-C, iOS 4.2+

if ([SomeClass class]) {
    // class exists
    SomeClass *instance = [[SomeClass alloc] init];
} else {
    // class doesn't exist
}

Veja a resposta do code007 para mais detalhes .


OS X ou versões anteriores do iOS

Class klass = NSClassFromString(@"SomeClass");
if (klass) {
    // class exists
    id instance = [[klass alloc] init];
} else {
    // class doesn't exist
}

Use NSClassFromString(). Se retornar nil, a classe não existe, caso contrário, ele retornará o objeto de classe que pode ser usado.

Esta é a forma recomendada de acordo com a Apple neste documento :

[...] Seu código testaria a existência de [uma] classe usando o NSClassFromString()qual retornará um objeto de classe válido se [a] classe existir ou nulo se não existir. Se a classe existe, seu código pode usá-la [...]

Sensível
fonte
Obrigado. Apenas para completar a resposta para os outros, uma vez que você detectou a classe, você a cria usando a Classinstância retornada por NSClassFromString(atribua a id) e invoca os seletores nessa instância.
psicótico de
2
Notavelmente, esta é a única maneira no OSX, mas não a "melhor" maneira no iOS (4.2+), embora funcione. Veja a resposta do code007 para iOS especificamente.
Ben Mosher de
E se minha classe não for uma classe real, mas sim uma estrutura C?
Nathan H
O Swift 4.1 está chegando com um novo cheque canImport. Proposta
dispatchMain
69

Para novos projetos que usam um SDK base do iOS 4.2 ou posterior, há esta nova abordagem recomendada que é usar o método de classe NSObject para verificar a disponibilidade de classes fracamente vinculadas em tempo de execução. ie

if ([UIPrintInteractionController class]) {
    // Create an instance of the class and use it.
} else {
    // Alternate code path to follow when the
    // class is not available.
}

fonte: https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/cross_development/Using/using.html#//apple_ref/doc/uid/20002000-SW3

Este mecanismo usa a macro NS_CLASS_AVAILABLE, que está disponível para a maioria dos frameworks no iOS (observe que pode haver alguns frameworks que ainda não suportam NS_CLASS_AVAILABLE - verifique a nota de lançamento do iOS para isso). Também pode ser necessária uma configuração de configuração extra, que pode ser lida no link de documentação da Apple fornecido acima; no entanto, a vantagem desse método é que você obtém verificação de tipo estático.

code007
fonte
3
Um pouco tarde para o jogo, mas acabei de me deparar com esse problema ao tentar construir o código contido, UIAlertControllerembora ainda suporte iOS 7. A resposta do code007 está correta, mas a configuração extra necessária é vincular fracamente (definir de Requiredpara Optional) UIKit em seu projeto (para esta situação, pelo menos).
Evan R