Qual é o equivalente Swift do respondsToSelector?

195

Pesquisei no Google, mas não consegui descobrir qual é o equivalente rápido respondsToSelector:.

Essa é a única coisa que pude encontrar ( alternativa rápida para o respondsToSelector :), mas não é muito relevante no meu caso, pois está verificando a existência do delegado, não tenho um delegado, só quero verificar se existe uma nova API ou não, quando em execução no dispositivo e, se não, voltar para uma versão anterior da API.

Gruntcakes
fonte
Todos estes são feitos para serem substituídos por Opcionais, e exercido com Opcional Encadeamento
Jack
Dado que a Apple recomenda explicitamente o uso NSClassFromStringe respondsToSelectorentre outras mecânicas para verificar a funcionalidade recém-implementada, tenho que acreditar que os mecanismos já estão em vigor ou já estarão lá antes do lançamento. Tente assistir ao Advanced Interop...vídeo da WWDC.
David Berry
2
@ Jack Wu Mas e se o novo método for algo novo introduzido em algo fundamental como UIApplication ou UIViewController. Esses objetos não são opcionais e o novo método não é opcional. Como você pode verificar se deve chamar, por exemplo, UIApplcation: newVersionOfMethodX ou deve chamar UIApplication: deprecatedVersionOfMethodX? (Dado que você pode construir e executar um aplicativo em Swift no iOS 7 é que isto vai ser um cenário muito comum)
Gruntcakes
A API que você está / preocupou-se com uma API da Apple?
GoZoner
@PotassiumPermanganate - é claro, se a resposta for 'sim, eu estava usando APIs da Apple', talvez ele possa usar o if #available(...)Swift 2.x para evitar o uso respondsToSelectorem primeiro lugar. Mas você sabia disso. ( apple.co/1SNGtMQ )
GoZoner

Respostas:

170

Como mencionado, na Swift na maioria das vezes você pode obter o que precisa com o ?operador desempacotador opcional . Isso permite que você chame um método em um objeto se e somente se o objeto existir (não nil) e o método for implementado.

No caso em que você ainda precisa respondsToSelector:, ele ainda está lá como parte doNSObject protocolo.

Se você estiver chamando respondsToSelector:um tipo de Obj-C no Swift, ele funcionará da mesma maneira que você esperaria. Se você o estiver usando em sua própria classe Swift, precisará garantir que sua classe derive NSObject.

Aqui está um exemplo de uma classe Swift que você pode verificar se responde a um seletor:

class Worker : NSObject
{
    func work() { }
    func eat(food: AnyObject) { }
    func sleep(hours: Int, minutes: Int) { }
}

let worker = Worker()

let canWork = worker.respondsToSelector(Selector("work"))   // true
let canEat = worker.respondsToSelector(Selector("eat:"))    // true
let canSleep = worker.respondsToSelector(Selector("sleep:minutes:"))    // true
let canQuit = worker.respondsToSelector(Selector("quit"))   // false

É importante que você não deixe de fora os nomes dos parâmetros. Neste exemplo, nãoSelector("sleep::") é o mesmo que .Selector("sleep:minutes:")

Erik
fonte
Estou tentando determinar se um novo método introduzido no iOS8 no UIApplication está presente e até agora não estou tendo sorte. Aqui está como eu estou tentando de acordo com o acima: se deixe presente = UIApplication.sharedApplication (). RespondesToSelector (Selector ("redactedAsStillUnderNDA")). No entanto, recebo um erro de compilação: "O valor vinculado em uma ligação condicional deve ser do tipo opcional".
Gruntcakes
4
Você precisará dividir essas linhas ou remover a let x = parte. Basicamente, a if let x = yestrutura é desembrulhar valores opcionais (semelhantes a !). Como você está se Boolrecuperando respondsToSelector, o compilador está reclamando que o resultado não é um tipo opcional ( Bool?).
Erik
O que aconteceria com os vardeclarados? Eles ainda respondem da mesma maneira? No Objective-C eu poderia fazer if ( [obj respondsToSelector:@selector(setSomeVar:)] ) { ... }por uma someVarpropriedade. Funciona da mesma maneira com vars no Swift?
Alejandro Iván
E se a instância opcional do View controller não for nula, mas o método ou a função não estiver implementada nesse controlador? Eu acho que você precisará verificar se o objeto responde ao seletor ou não.
Ashish Pisey
Estou recebendo "O valor do tipo 'UIViewController' não tem nenhum membro 'respondesToSelector'"
Michał Ziobro
59

Não há substituição Swift real.

Você pode verificar da seguinte maneira:

someObject.someMethod?()

Isso chama o método someMethodapenas se definido no objetosomeObject mas você pode usá-lo apenas para @objcprotocolos que declararam o método como optional.

O Swift é inerentemente um idioma seguro, portanto, toda vez que você chama um método, o Swift precisa saber que o método existe. Nenhuma verificação de tempo de execução é possível. Você não pode simplesmente chamar métodos aleatórios em objetos aleatórios.

Mesmo no Obj-C, você deve evitar essas coisas sempre que possível, porque não funciona bem com o ARC (o ARC aciona avisos para performSelector: ).

No entanto, ao verificar as APIs disponíveis, você ainda pode usar respondsToSelector:, mesmo se Swift, se estiver lidando com NSObjectinstâncias:

@interface TestA : NSObject

- (void)someMethod;

@end

@implementation TestA

//this triggers a warning

@end   


var a = TestA()

if a.respondsToSelector("someMethod") {
   a.someMethod()
}
Sulthan
fonte
1
Portanto, os aplicativos agora precisam saber explicitamente em qual versão do sistema operacional estão executando? Quero chamar um método que só existe no iOS8 e, se não estiver lá, use a versão antiga do iOS7. Então, em vez de verificar a presença / ausência do novo método, preciso descobrir se estou no iOS8 ou não? Swift é bom, mas isso parece um pouco desajeitado.
Gruntcakes
@ sulthan Não sei de onde você vem afirmando que não deveria estar usando, respondsToSelector:pois a recomendação da Apple é explicitamente que você deveria fazê-lo. Veja aqui
David Berry
@ David Sim, você encontrou um dos poucos casos em que respondsToSelector:é realmente bom ter. Mas se você passar pela referência Swift, eles falam sobre o uso de subclasses para diferentes versões do sistema.
Sulthan
Se esse não é o caso do OP, e eles estão falando sobre o caso do protocolo opcional, eles também permitem explicitamente isso usando o encadeamento opcional (como você apontou). De qualquer maneira, dizer "você deve evitar fazer o que a Apple lhe disse explicitamente" parece um péssimo conselho. A Apple "sempre" forneceu mecanismos para determinar e exercer a funcionalidade opcional em tempo de execução.
David Berry
1
@Honey No Swift, geralmente usamos diretivas de disponibilidade, por exemplo, if #available(iOS 10) {e depois chamamos o método diretamente.
Sulthan
40

Atualize 20 de março de 2017 para a sintaxe do Swift 3:

Se você não se importa se o método opcional existe, basta chamar delegate?.optionalMethod?()

Caso contrário, usar guardé provavelmente a melhor abordagem:

weak var delegate: SomeDelegateWithOptionals?

func someMethod() {
    guard let method = delegate?.optionalMethod else {
        // optional not implemented
        alternativeMethod()
        return
    }
    method()
}

Resposta original:

Você pode usar a abordagem "if let" para testar um protocolo opcional como este:

weak var delegate: SomeDelegateWithOptionals?

func someMethod() {
  if let delegate = delegate {
    if let theMethod = delegate.theOptionalProtocolMethod? {
      theMethod()
      return
    }
  }
  // Reaching here means the delegate doesn't exist or doesn't respond to the optional method
  alternativeMethod()
}
hyouuu
fonte
4
Se o método for delegado? .theOptionalProtocolMethod
john07
1
Exemplo de sintaxe de seletor quando existem vários candidatos:let theMethod = delegate.userNotificationCenter(_:willPresent:withCompletionHandler:)
Cœur
11

Parece que você precisa definir seu protocolo como subprotocolo de NSObjectProtocol ... então você obterá o método respondsToSelector

@objc protocol YourDelegate : NSObjectProtocol
{
    func yourDelegateMethod(passObject: SomeObject)
}

observe que apenas especificar @objc não foi suficiente. Você também deve ter cuidado para que o delegado real seja uma subclasse do NSObject - que no Swift pode não ser.

Matej Ukmar
fonte
10

Se o método que você está testando for definido como um método opcional em um protocolo @objc (que parece o seu caso), use o padrão de encadeamento opcional como:

if let result = object.method?(args) {
  /* method exists, result assigned, use result */
}
else { ... }

Quando o método for declarado como retornando Void, basta usar:

if object.method?(args) { ... }

Vejo:

“Chamando métodos por meio de encadeamento opcional”
Trecho de: Apple Inc. “A linguagem de programação rápida”.
iBooks. https://itun.es/us/jEUH0.l

GoZoner
fonte
Isso só funcionaria se o método retornar algo, e se for um método nulo?
Gruntcakes
1
Se o uso vazio simplesmente if object.method?(args) { ... }- a chamada do método, quando existe, irá devolver Voido que não énil
GoZoner
1
No entanto, isso resulta em "Tipo de vácuo não está em conformidade com o protocolo" Valor lógico ""
Gruntcakes
Consulte a documentação referenciada (página 311-312).
GoZoner
"Se o método que você está testando for definido como um método opcional em um protocolo @objc" Ou se objecttiver um tipo AnyObject, você poderá testar qualquer método @objc.
newacct
9

As funções são tipos de primeira classe no Swift, para que você possa verificar se uma função opcional definida em um protocolo foi implementada comparando-a com zero:

if (someObject.someMethod != nil) {
    someObject.someMethod!(someArgument)
} else {
    // do something else
}
Christopher Pickslay
fonte
1
E se o método estiver sobrecarregado? Como podemos verificar um específico?
Nuno Gonçalves
8

Para swift3

Se você quiser chamar o método, execute o código abaixo.

self.delegate?.method?()

Jason Yu
fonte
7

No Swift 2, a Apple introduziu um novo recurso chamado API availability checking, que pode ser um substituto para o respondsToSelector:método. A comparação de trechos de código a seguir é copiada da WWDC2015 Session 106 What's New in Swift, que eu pensei que poderia ajudá-lo, verifique se você precisar saber mais.

A velha abordagem:

@IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
    if dropButton.respondsToSelector("setSpringLoaded:") {
        dropButton.springLoaded = true
    }
}

A melhor abordagem:

@IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
    if #available(OSX 10.10.3, *) {
        dropButton.springLoaded = true
    }
}
tounaobun
fonte
Eu acho que é uma solução muito melhor do que usar a respondsToSelectorabordagem no Swift. Isso também ocorre porque a verificação do seletor não era a melhor solução para esse problema (verificação da disponibilidade) em primeiro lugar.
JanApotheker
6

For swift 3.0

import UIKit

@objc protocol ADelegate : NSObjectProtocol {

    @objc optional func hi1()
    @objc optional func hi2(message1:String, message2:String)
}

class SomeObject : NSObject {

    weak var delegate:ADelegate?

    func run() {

        // single method
        if let methodHi1 = delegate?.hi1 {
            methodHi1()
        } else {
            print("fail h1")
        }

        // multiple parameters
        if let methodHi2 = delegate?.hi2 {
            methodHi2("superman", "batman")
        } else {
            print("fail h2")
        }
    }
}

class ViewController: UIViewController, ADelegate {

    let someObject = SomeObject()

    override func viewDidLoad() {
        super.viewDidLoad()

        someObject.delegate = self
        someObject.run()
    }

    // MARK: ADelegate
    func hi1() {

        print("Hi")
    }

    func hi2(message1: String, message2: String) {

        print("Hi \(message1) \(message2)")
    }
}
Kakashi
fonte
4

Atualmente (Swift 2.1), você pode verificá-lo usando três maneiras:

  1. Usando respondsToSelector respondido por @Erik_at_Digit
  2. Usando '?' respondido por @Sulthan

  3. E usando o as?operador:

    if let delegateMe = self.delegate as? YourCustomViewController
    {
       delegateMe.onSuccess()
    }

Basicamente, depende do que você está tentando alcançar:

  • Se, por exemplo, a lógica do aplicativo precisar executar alguma ação e o delegado não estiver definido ou o delegado apontado não implementou o método onSuccess () (método de protocolo), as opções 1 e 3 são a melhor opção, embora eu use opção 3, que é a maneira rápida.
  • Se você não quiser fazer nada quando o delegado for nulo ou o método não for implementado, use a opção 2.
OhadM
fonte
2

Eu apenas implementei isso em um projeto, veja o código abaixo. Como mencionado por @Christopher Pickslay, é importante lembrar que as funções são cidadãos de primeira classe e, portanto, podem ser tratadas como variáveis ​​opcionais.

@objc protocol ContactDetailsDelegate: class {

    optional func deleteContact(contact: Contact) -> NSError?
}

...

weak var delegate:ContactDetailsDelegate!

if let deleteContact = delegate.deleteContact {
    deleteContact(contact)
}
Bulwinkel
fonte
E se o método estiver sobrecarregado? Como alguém faria isso?
Nuno Gonçalves
2

outra sintaxe possível por swift ..

 if let delegate = self.delegate, method = delegate.somemethod{
        method()
    }
Janub
fonte
2

Quando comecei a atualizar meu projeto antigo para o Swift 3.2, só precisava alterar o método de

respondsToSelector(selector)

para:

responds(to: selector)
chAlexey
fonte
0

Eu uso guard let else, para que possa fazer algumas coisas padrão se a função delegada não for implementada.

@objc protocol ViewController2Delegate: NSObjectProtocol {

    optional func viewController2(controller: ViewController2, didSomethingWithStringAndReturnVoid string: String)

    optional func viewController2(controller: ViewController2, didSomethingWithStringAndReturnString string: String) -> String
}

class ViewController2: UIViewController {

    weak var delegate: ViewController2Delegate?        

    @IBAction func onVoidButtonClicked(sender: AnyObject){

        if (delegate != nil && delegate!.respondsToSelector(Selector("viewController2:didSomethingWithStringAndReturnVoid:"))) {
            NSLog("ReturnVoid is implemented")

            delegate!.viewController2!(self, didSomethingWithStringAndReturnVoid: "dummy")
        }
        else{
            NSLog("ReturnVoid is not implemented")
            // Do something by default
        }
    }

    @IBAction func onStringButtonClicked(sender: AnyObject){

        guard let result = delegate?.viewController2?(self, didSomethingWithStringAndReturnString: "dummy") else {
            NSLog("ReturnString is not implemented")
            // Do something by default
            return
        }

        NSLog("ReturnString is implemented with result: \(result)")
    }
}
dichen
fonte
-1

Swift 3:

protocolo

@objc protocol SomeDelegate {
    @objc optional func method()
}

Objeto

class SomeObject : NSObject {

weak var delegate:SomeObject?

func delegateMethod() {

     if let delegateMethod = delegate?.method{
         delegateMethod()
     }else {
        //Failed
     }

   }

}
etzuk
fonte
-4

O equivalente é o? operador:

var value: NSNumber? = myQuestionableObject?.importantMethod()

importantMethod só será chamado se myQuestionableObject existir e implementá-lo.

Ben Gottlieb
fonte
Mas e se myQuestionalObject for algo como UIApplication (que, portanto, existirá e, portanto, verificar sua existência não tiver sentido) e importandMethod () for um novo método introduzido no iOS N e você desejar determinar se pode chamá-lo ou se deve chamar o velho deprecatedImportantMethod () no iOS N-1?
Gruntcakes
2
você também precisa de um ?depoisimportantMethod
newacct