Как вызвать метод класса с помощью PerformSelector() для AnyClass в Swift?

В ObjC вы можете просто вызвать метод класса, используя метод класса из NSObject.

[Machine performSelector:@selector(calculate:) withObject:num];

Но как это сделать в Swift 2.2?

@objc(Machine) // put it here, so you can simply copy/paste into Playground
class Machine: NSObject {
    static func calculate(param: NSNumber) -> String {
        if param.integerValue > 5 {
            return "42"
        }
        return "42" // there is only 1 answer to all the questions :D
    }
}

if let aClass = NSClassFromString("Machine") {
    let sel = #selector(Machine.calculate(_:))
    let num = NSNumber(integer: 1337)
    let answer = aClass.performSelector(sel, withObject: num) // compiler error
    // let answer = aClass.calculate(num)                     // <-- this works
    print(answer)
}

С этим кодом я получаю следующую ошибку компилятора:

ошибка: невозможно вызвать «performSelector» со списком аргументов типа «(Selector, withObject: NSNumber)»

Что мне здесь не хватает?


person Buju    schedule 24.03.2016    source источник
comment
Это точно не бессмысленно, а вдруг вы захотите использовать класс из внешнего источника? НАПРИМЕР. читая имя класса из строки в plist, вы собираетесь написать условие if на основе каждого возможного объекта?   -  person Knight0fDragon    schedule 24.03.2016


Ответы (2)


AnyClass изначально не соответствует NSObjectProtocol. Мне пришлось преобразовать aClass в NSObjectProtocol, чтобы использовать performSelector (performSelector:withObject: подключен к Swift как метод для NSObjectProtocol):

Свифт 3:

if let aClass = NSClassFromString("Machine") {
    let sel = #selector(Machine.calculate(param:))
    let num = NSNumber(value: 1337)

    if let myClass = aClass as? NSObjectProtocol {
        if myClass.responds(to: sel) {
            let answer = myClass.perform(sel, with: num).takeRetainedValue() // this returns AnyObject, you may want to downcast to your desired type
            print(answer) // "42\n"
        }
    }
}

Свифт 2.х:

(aClass as! NSObjectProtocol).performSelector(sel, withObject: num) // Unmanaged<AnyObject>(_value: 42) 

Немного безопаснее:

if let aClass = NSClassFromString("Machine") {
    let sel = #selector(Machine.calculate(_:))
    let num = NSNumber(integer: 1337)

    if let myClass = aClass as? NSObjectProtocol {
        if myClass.respondsToSelector(sel) {
            let answer = myClass.performSelector(sel, withObject: num).takeUnretainedValue()
            print(answer) // "42\n"
        }
    }
}

performSelector возвращает объект Unmanaged, поэтому требуется takeUnretainedValue() (или, при желании, takeRetainedValue(), если вы хотите передать право собственности на память).

person JAL    schedule 24.03.2016
comment
Это забавно, потому что кастинг как? AnyObject работал для меня - person Knight0fDragon; 24.03.2016
comment
@Knight0fDragon Приведение к AnyObject будет работать, потому что AnyClass на самом деле является псевдонимом метатипа AnyObject.Type. Вы, вероятно, получили предупреждение о том, что отбрасывание прошло успешно, потому что они одного типа. Вероятно, не очень хорошая идея приводить класс к объекту. - person JAL; 24.03.2016
comment
@Knight0fDragon Вы можете использовать performSelector только для типов Objective-C, а не для чистых типов Swift. Попробуй. Вот почему я проверяю соответствие NSObjectProtocol. - person JAL; 25.03.2016
comment
У меня нет предупреждения на моем экране, и не будет ли NSClassFromString автоматически терпеть неудачу для быстрых типов? - person Knight0fDragon; 25.03.2016
comment
@Knight0fDragon Хм, хорошее замечание по поводу NSClassFromString. Но я все еще вижу предупреждение, если я пытаюсь привести класс как AnyObject: if let myClass = aClass as? AnyObject { ... } Я использую Xcode 7.3. - person JAL; 25.03.2016
comment
да, странно, используйте мой код, и вы его не увидите, может быть, это потому, что это AnyClass? ? - person Knight0fDragon; 25.03.2016
comment
ааа, мне нужно снова обновиться до 2.2, похоже, моя установка пошла не так, я вернусь к вам с предупреждением - person Knight0fDragon; 25.03.2016
comment
Благодарность! Я знал, что мне не хватает некоторого соответствия, но не знал, какой протокол и где его искать;) - person Buju; 25.03.2016
comment
Наконец-то Swift 2.2 снова заработал, во всяком случае, да, все еще не предупреждает. Кроме того, поскольку мы знаем, что NSClass собирается вернуть член NSClass, когда он действителен, я не думаю, что вторая переменная нужна, вы можете просто проверить, является ли aClass член NSObjectProtocol и посмотрите, отвечает ли он там на селектор - person Knight0fDragon; 28.03.2016
comment
@Knight0fDragon да, этот код, вероятно, можно дополнительно оптимизировать, как if let aClass = NSClassFromString("Machine") as? NSObjectProtocol { ... }, но я оставил ответ расширенным, чтобы пользователи могли видеть процесс приведения от AnyClass к NSObjectProtocol. Я думаю, что так легче читать, и всегда можно оптимизировать в качестве упражнения для читателя. - person JAL; 29.03.2016
comment
@Buju Да, я забыл об этом раньше, поэтому я хотел сделать свой код немного безопаснее на случай, если другой пользователь найдет его и решит использовать. - person JAL; 29.03.2016
comment
Вы можете использовать PerformSelector только для типов Objective-C, а не для чистых типов Swift. Но на самом деле во время выполнения чистые типы Swift делают поддерживают .respondsToSelector(), .performSelector() и т. д. Таким образом, приведение к AnyObject (чтобы преодолеть незнание такой поддержки во время компиляции) и их использование делает< /i> работают без необходимости соответствия типа NSObjectProtocol. - person newacct; 03.04.2016
comment
@newacct А, ты прав! На самом деле я только что задал вопрос об этом здесь. Несмотря на то, что чистые классы Swift представлены как SwiftObject в среде выполнения Objective-C, вам необходимо явно объявить свой объект как AnyObject или NSObjectProtocol, чтобы запустить на нем селекторы Objective-C. - person JAL; 28.04.2016
comment
Как ни странно, Swift 4 выдает предупреждение о попытке преобразовать AnyClass в NSObjectProtocol, что меня раздражает. Во время выполнения он все еще работает, но пытается очистить проект от предупреждений. У кого-нибудь есть идея? - person Legoless; 29.08.2017

Если вы хотите выполнить расчет на машине, просто выполните:

Machine.calculate(NSNumber(integer: 1337))

Если вам нужно вызвать селектор выполнения, выполните:

if let aClass = NSClassFromString("Machine") as? AnyObject
{
    let sel = #selector(Machine.calculate(_:))
    let num = NSNumber(integer: 1337)
    let answer = aClass.performSelector(sel, withObject: num)
    print(answer)
}
person Knight0fDragon    schedule 24.03.2016