Подтвердить что ты не робот

Альтернатива для выполнения выбора в Swift?

Семейство методов performSelector недоступно в Swift. Итак, как вы можете вызвать метод в объекте @objc, где метод, который будет вызываться, выбран во время выполнения и не известен во время компиляции? NSInvocation, по-видимому, также недоступен в Swift.

Я знаю, что в Swift вы можете отправить любой метод (для которого существует объявление метода @objc), тип AnyObject, аналогичный id в Objective-C. Тем не менее, это по-прежнему требует жесткого кодирования имени метода во время компиляции. Есть ли способ динамически выбирать его во время выполнения?

4b9b3361

Ответ 1

Использование замыканий

class A {
    var selectorClosure: (() -> Void)?

    func invoke() {
        self.selectorClosure?()
    }
}

var a = A()
a.selectorClosure = { println("Selector called") }
a.invoke()

Обратите внимание, что это ничего нового, даже в Obj-C новые API предпочитают использовать блоки над performSelector (сравните UIAlertView, который использует respondsToSelector: и performSelector: для вызова методов делегирования, с новым UIAlertController).

Использование performSelector: всегда небезопасно и плохо работает с ARC (следовательно, предупреждения ARC для performSelector:).

Ответ 2

Как и в Xcode 7, в Swift доступно полное семейство методов performSelector, включая performSelectorOnMainThread() и performSelectorInBackground(). Наслаждайтесь!

Ответ 3

Подход A

Используйте NSThread.detachNewThreadSelector, хорошо, что этот подход заключается в том, что мы можем присоединить объект к сообщению. Пример кода в ViewController:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    let delay = 2.0 * Double(NSEC_PER_SEC)
    var time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
    dispatch_after(time, dispatch_get_main_queue(), {
        NSThread.detachNewThreadSelector(Selector("greetings:"), toTarget:self, withObject: "sunshine")
        })
}

func greetings(object: AnyObject?) {
    println("greetings world")
    println("attached object: \(object)")
}

Журнал консоли:

мир приветствий

прикрепленный объект: солнечный свет

Подход B

Эта альтернатива была обнаружена ранее, я также тестировал устройство и симулятор. Идея заключается в использовании следующего метода UIControl:

func sendAction(_ action: Selector, to target: AnyObject!, forEvent event: UIEvent!)

Пример кода в ViewController:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    var control: UIControl = UIControl()
    control.sendAction(Selector("greetings"), to: self, forEvent: nil) // Use dispatch_after to invoke this line as block if delay is intended 
}

func greetings() {
    println("greetings world")
}

Журнал консоли:

мир приветствий

Подход C

NSTimer

class func scheduledTimerWithTimeInterval(_ seconds: NSTimeInterval,
                                      target target: AnyObject!,
                                 selector aSelector: Selector,
                                  userInfo userInfo: AnyObject!,
                                    repeats repeats: Bool) -> NSTimer!

Ответ 4

Swift 3

perform(#selector(someSelector), with: nil, afterDelay: 1.0, inModes: [.commonModes])

Ответ 5

В соответствии с ответом @JTerry "Вам не нужны селектора в Swift", вы можете назначить фактические методы для переменных. Мое решение было следующим (мне нужен один параметр в методе):

class SettingsMenuItem: NSObject {
    ...
    var tapFunction: ((sender: AnyObject?) -> ())?
}

И затем в контроллере, который я объявил, назначил и запускал функцию таким образом:

class SettingsViewController: UITableViewController {

    func editProfile(sender: AnyObject?) {
        ...
    }

    ...

    menuItem.tapFunction = editProfile

    ...

    if let tapFunction = menuItem.tapFunction {
        tapFunction(sender: self)
    }


}

Ответ 6

Вы можете использовать это в Swift

var timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector:  Selector("someSelector"), userInfo: nil, repeats: false)


func someSelector() {
// Something after a delay
}

этим вы можете сделать то, что выполняется функцией performSelector в Objective-C

Ответ 7

Я тоже боролся с этим. Наконец я понял, что мне не нужно использовать цели или селектора. Для меня решение было назначить func переменной и вызвать эту переменную. Это даже работает, если вы называете это из других классов. Вот краткий пример:

func Apple() ->Int
{
    let b = 45;
    return b;
}

func Orange()->Int
{
    let i = 5;
    return i;
}

func Peach()
{
    var a = Apple; // assign the var a the Apple function
    var b = Orange; // assisgn the var b to the Orange function

    let c = a(); // assign the return value of calling the 'a' or Apple function to c
    let d = b(); // assign the return value of calling the 'b' or Orange function d

    Pear(a, b)
}

func Pear(x:()->Int, y:()->Int)->Int
{
    let w = (x()+y()); // call the x function, then the y function and add the return values of each function.
    return w; // return the sum
}

Peach();

Ответ 8

Swift 3.1
Для стандартных закрытий проектов Swift - это элегантное решение, уже рассмотренное в Sulthan answer. Вызов методов динамически с использованием имен селекторных строк имеет смысл, если каждый из них зависит от устаревшего кода/библиотек Objective-C.

Подклассы NSObject могут получать сообщения, попытка отправить их в чистый класс Swift приведет к сбою.

#selector(mySelectorName) может разрешать типизированные имена селекторов только в исходном файле класса.
Пожертвовав проверку типа, селектор можно получить с помощью NSSelectorFromString(...)
(это не безопаснее в любом случае по сравнению с Selector("selectorName:arg:") просто не возникает предупреждение).

Вызов NSObject метода экземпляра подкласса

let instance : NSObject = fooReturningObjectInstance() as! NSObject
instance.perform(#selector(NSSelectorFromString("selector"))
instance.perform(#selector(NSSelectorFromString("selectorArg:"), with: arg)
instance.perform(#selector(NSSelectorFromString("selectorArg:Arg2:"), with: arg, with: arg2)

также с основными и фоновыми вариантами потока:

instance.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
instance.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)

Есть некоторые ограничения:

  • Он может принимать только 0-2 аргументов
  • аргументы типа значения, такие как целые числа и селекторы, не работают.
  • не может обрабатывать возвращаемые типы значений
  • возвращает объекты как Unmanaged<AnyObject>

Таким образом, этот подход с низким уровнем усилий удобен, когда не нужны возвращаемые результаты и аргументы типа значения.

Извлечение NSObject метода времени выполнения IMP позволяет сделать типизированный вызов с правильными аргументами и возвращаемым типом. @convention(c)(types)->type позволяет отличать результат IMP к совместимой функции закрытия Swift.

В @convention(c) не все типы разрешены

  • Для классов используйте Any или AnyClass
  • Для объектов используется любой или точный тип класса, если его символ доступен
  • Для типов значений используйте соответствующий тип
  • Для void * используйте OpaquePointer

Это по определению небезопасно, а если сделано некорректно, это приведет к сбоям и побочным эффектам.

Каждый метод Objective-C на уровне C содержит два скрытых аргумента, соответствующих objc_msgSend(id self, SEL op, ...), которые должны быть включены в тип функции как @convention(c)(Any?,Selector, ... )

let instance : NSObject = fooReturningObjectInstance() as! NSObject
let selector : Selector = NSSelectorFromString("selectorArg:")
let methodIMP : IMP! = instance.method(for: selector)
unsafeBitCast(methodIMP,to:(@convention(c)(Any?,Selector,Any?)->Void).self)(instance,selector,arg) 

Это статические эквиваленты perform(...)

NSObject.perform(NSSelectorFromString("selector"))
NSObject.perform(NSSelectorFromString("selectorArg:"), with: arg)
NSObject.perform(NSSelectorFromString("selectorArg:Arg2:"), with: arg, with: arg2)
NSObject.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
NSObject.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)

Ограничения:

  • Все проблемы типа, упомянутые ранее
  • Класс приемника должен иметь определенный символ

Извлечение статического метода времени выполнения IMP и типов обработки, @convention(c) применяется

let receiverClass = NSClassFromString("MyClass")
let selector : Selector = NSSelectorFromString("selectorArg:")
let methodIMP : IMP! = method_getImplementation(class_getClassMethod(receiverClass, selector))
let result : NSObject = unsafeBitCast(methodIMP,to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(receiverClass,selector,arg) as! NSObject

Нет практических оснований для этого, но objc_msgSend можно использовать динамически.

let instance : NSObject = fooReturningObjectInstance() as! NSObject
let handle : UnsafeMutableRawPointer! = dlopen("/usr/lib/libobjc.A.dylib", RTLD_NOW)
let selector : Selector = NSSelectorFromString("selectorArg:")
unsafeBitCast(dlsym(handle, "objc_msgSend"), to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(instance,selector,arg)
dlclose(handle)

То же самое для NSInvocation (это только забавное упражнение, не)

class Test : NSObject
{
    var name : String? {
        didSet {
            NSLog("didSetCalled")
        }
    }

    func invocationTest() {
        let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromString("NSInvocation"), NSSelectorFromString("invocationWithMethodSignature:"))),to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromString("NSInvocation"),NSSelectorFromString("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromString("methodSignatureForSelector:"))!,to:(@convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromString("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject
        unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setSelector:")),to:(@convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromString("setSelector:"),#selector(setter:name))
        var localName = name
        withUnsafePointer(to: &localName) { unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setArgument:atIndex:")),to:(@convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromString("setArgument:atIndex:"), OpaquePointer($0),2) }
        invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
    }
}

Ответ 9

Да, мы можем использовать swizzling для раскрытия желаемых методов!

Просто добавьте этот extension и префикс всех вызовов с символом 🚀.

import Foundation

private var dispatchOnceToken: dispatch_once_t = 0

private var selectors: [Selector] = [
    "performSelector:",
    "performSelector:withObject:",
    "performSelector:withObject:withObject:",
    "performSelector:withObject:afterDelay:inModes:",
    "performSelector:withObject:afterDelay:",
]

private func swizzle() {
    dispatch_once(&dispatchOnceToken) {
        for selector: Selector in selectors {
            let 🚀selector = Selector("🚀\(selector)")
            let method = class_getInstanceMethod(NSObject.self, selector)

            class_replaceMethod(
                NSObject.self,
                🚀selector,
                method_getImplementation(method),
                method_getTypeEncoding(method)
            )
        }
    }
}

extension NSObject {

    func 🚀performSelector(selector: Selector) -> AnyObject? {
        swizzle()
        return self.🚀performSelector(selector)
    }

    func 🚀performSelector(selector: Selector, withObject object: AnyObject?) -> AnyObject? {
        swizzle()
        return self.🚀performSelector(selector, withObject: object)
    }

    func 🚀performSelector(selector: Selector, withObject object1: AnyObject?, withObject object2: AnyObject?) -> AnyObject? {
        swizzle()
        return self.🚀performSelector(selector, withObject: object1, withObject: object2)
    }

    func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval, inModes modes: [AnyObject?]?) {
        swizzle()
        self.🚀performSelector(selector, withObject: object, afterDelay: delay, inModes: modes)
    }

    func 🚀performSelector(selector: Selector, withObject object: AnyObject?, afterDelay delay: NSTimeInterval) {
        swizzle()
        self.🚀performSelector(selector, withObject: object, afterDelay: delay)
    }

}

Ответ 10

фактический синтаксис для очереди отправки следующий.

dispatch_after(1, dispatch_get_main_queue()) { () -> Void in
        self.loadData() // call your method.
    }

Ответ 11

Иногда (особенно если вы используете шаблон target/action), вам может потребоваться использовать метод -[UIApplication sendAction:to:from:forEvent:] (для iOS), поэтому в Swift это может быть примерно так:

UIApplication.sharedApplication()
    .sendAction(someSelector, to: someObject, from: antotherObject, forEvent: someEvent)

Ответ 12

Я не знаю точно с тех пор, как, но Apple вернула выполнитьSelector в Xcode 7.1.1 (по крайней мере, той версии, которую я использую).

В моем приложении, которое я сейчас создаю, я вызываю различные функции с похожими именами функций в UIView, сгенерированном из CoreAnimator (отличное приложение, BTW), поэтому performSelector очень удобен. Вот как я его использую:

//defines the function name dynamically.  the variables "stepN" and "dir" are defined elsewhere. 
let AnimMethod = "addStep\(stepN)\(dir)Animation"

//prepares the selector with the function name above           
let selector: Selector = NSSelectorFromString(AnimMethod)

//calls the said function in UIView named "meter"            
meter.performSelector(selector)

Ответ 13

Я использую следующее решение:

// method will be called after delay
func method1() {

    ......    
}

// to replace performSelector
// delay 100 ms
let time : dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_MSEC/(USEC_PER_SEC*10)))
dispatch_after(time, dispatch_get_main_queue(), {
        self.method1()
})

Ответ 14

У меня есть ситуация, когда селектор построен со строковым литералом, который поступает из файла plist. Таким образом, самый быстрый способ выполнить некоторый селектор в swift был решен с помощью следующего кода

var timer = NSTimer(timeInterval: 1000, target: self, selector: Selector(someString), userInfo: nil, repeats: false)
timer.fire()
timer.invalidate()

Ответ 15

Пример реального мира в быстром комментарии "Матея Укмара" к ответу "J Терри":

class Button {
    var title:String = "The big button"
    var selector: ((sender: AnyObject?, type:String) -> ())?/*this holds any method assigned to it that has its type signature*/
    func click(){
        selector!(sender: self,type: "click")/*call the selector*/
    }
    func hover(){
        selector!(sender: self,type: "hover")/*call the selector*/
    }
}
class View {
    var button = Button()
    init(){
        button.selector = handleSelector/*assign a method that will receive a call from the selector*/
    }
    func handleSelector(sender: AnyObject?,type:String) {
        switch type{
            case "click": Swift.print("View.handleSelector() sender: " + String(sender!.dynamicType) + ", title: " + String((sender as! Button).title) + ", type: " + type)
            case "hover": Swift.print("View.handleSelector() sender: " + String(sender!.dynamicType) + ", title: " + String((sender as! Button).title) + ", type: " + type)
            default:break;
        }
    }
}
let view:View = View()
view.button.click()/*Simulating button click*/
view.button.hover()/*Simulating button hover*/
//Output: View.handleSelector() sender: Button, title: The big button, type: click
//Output: View.handleSelector() sender: Button, title: The big button, type: hover