替代Swift中的performSelector?

时间:2014-06-11 08:44:48

标签: swift performselector

performSelector方法系列are not available in Swift。那么如何在@objc对象上调用一个方法,在运行时选择要调用的方法,而在编译时不知道? NSInvocation显然在Swift中也不可用。

我知道在Swift中,您可以将任何方法(对于@objc方法声明可见)发送到类型AnyObject,类似于Objective-C中的id。但是,这仍然需要您在编译时对方法名称进行硬编码。有没有办法在运行时动态选择它?

16 个答案:

答案 0 :(得分:17)

使用闭包

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(因此performSelector:的ARC警告)。

答案 1 :(得分:15)

从Xcode 7开始,swift中提供了完整的performSelector方法系列,包括performSelectorOnMainThread()performSelectorInBackground()。享受!

答案 2 :(得分:11)

方法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!

答案 3 :(得分:8)

Swift 3

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

答案 4 :(得分:7)

根据@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)
    }


}

答案 5 :(得分:6)

你可以在Swift中使用它

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


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

由此您可以执行Objective-C

中的performSelector执行的操作

答案 6 :(得分:4)

我也在努力解决这个问题。我终于意识到我不需要使用目标或选择器。对我来说,解决方案是将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();

答案 7 :(得分:4)

Swift 3.1
对于标准的Swift项目,闭包是Sulthan's answer中已涵盖的优雅解决方案。 如果一个依赖于传统的Objective-C代码/库或者想要调用私有API,那么使用选择器字符串名称动态调用方法是有意义的。

只有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)

正如 https://stackoverflow.com/a/48644264/5329717中 iOS_MIB 所述 这不等同

DispatchQueue.main.async {
   //perform selector
}

和后台线程变体:

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

但是有一些限制:

  • 它只能采取0-2参数
  • 值类型参数,如整数和选择器不能正常工作
  • 无法处理返回值类型
  • 将对象返回为Unmanaged<AnyObject>

因此,当不需要返回结果和值类型参数时,这种低效方法很方便。

获取NSObject运行时方法IMP允许使用适当的参数和返回类型进行类型化调用。 @convention(c)(types)->type允许将IMP结果转换为兼容的Swift闭包函数。

@convention(c)中,并非所有类型都被允许

  • 对于类使用Any或AnyClass
  • 对象使用任何或完全类型(如果其符号可用)
  • 对于值类型,请使用相关类型
  • For void *使用OpaquePointer

根据定义,这是 不安全 ,如果操作不正确会导致崩溃和副作用。

C级别的每个Objective-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)
    }
}

答案 8 :(得分:3)

嗯,我们可以用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)
    }

}

答案 9 :(得分:2)

调度队列的实际语法如下。

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

答案 10 :(得分:2)

有时(特别是如果你使用的是target/action模式)你可能不得不使用方法-[UIApplication sendAction:to:from:forEvent:](对于iOS),所以在Swift中它可能是这样的事情:

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

答案 11 :(得分:2)

我从来没有确切知道,但是Apple在Xcode 7.1.1中带回了performSelector(至少是我使用的版本)。

在我正在构建的应用程序中,我在从CoreAnimator(优秀应用程序,BTW)生成的UIView中调用具有类似functionNames的各种函数,因此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)

答案 12 :(得分:0)

我使用以下解决方案:

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

答案 13 :(得分:0)

我有一种情况,其中selector使用来自plist文件的字符串文字构造。 因此,使用下一个代码

解决了在swift中执行某个选择器的最快方法
var timer = NSTimer(timeInterval: 1000, target: self, selector: Selector(someString), userInfo: nil, repeats: false)
timer.fire()
timer.invalidate()

答案 14 :(得分:0)

Matej Ukmar&#34;中的一个真实世界的例子。评论&#34; J Terry&#34;回答:

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

答案 15 :(得分:0)

只是该主题的另一个输入。

我有时会不得不“间接”调用函数/方法。示例:为特定单元格调用单个函数。我经常使用结构数组来定义tabelView行为。

我以前使用过 PerformSelector 等,但是在快速程序中它看起来总是“奇怪”,所以我做了一些研究,从那时起,我就使用了间接函数调用。

这是我的操场上用来测试语法和行为的快速示例...(xCode 9.4.1)

// Test for indirect function calls

// ------------------------------------------------------------------------
// functions we want to call inderectly
func function1() {
    print("function1 active")
}

func function2() {
    print("function2 active")
}

func function3() {
    print("function3 active")
}

func function4(_ parameter: Int)  {

    print("function4 use the parameter: \(parameter)")
}


// ------------------------------------------------------------------------
// data structures

// a struct to build array items
struct functionCallTestStruct {

    // struct properties
    let what: String            // a string as an example for other variables
    let functionToCall : ()     // the function as an array element
    var functionWithParameter : (Int) -> () // the function as an array element
    let parameterForFunction : Int


    // Initializer
    init(_ what: String,
         _ functionToCall: (),
         _ functionWithParameter: @escaping (Int) -> (),
         _ parameterForFunction: Int) {

        self.what = what
        self.functionToCall = functionToCall
        self.functionWithParameter = functionWithParameter
        self.parameterForFunction = parameterForFunction
    }
}

// the array which holds the functions we want to call
let functionTestArray : [functionCallTestStruct] = [

    functionCallTestStruct("We will call the first function",  function1(), function4(_:), 10),
    functionCallTestStruct("We will call the second function", function2(), function4(_:), 11),
    functionCallTestStruct("We will call the third function",  function3(), function4(_:), 12),
]

// ------------------------------------------------------------------------
// Test program

// a loop over the array
for i in 0 ..< functionTestArray.count {

    // print explanation (be aware: print is quite lame, .. see the output ;-))
    print(functionTestArray[i].what)

    // and with this we indirectly call the functions
    functionTestArray[i].functionToCall
    let myParameter = functionTestArray[i].parameterForFunction
    functionTestArray[i].functionWithParameter(myParameter)
}

给出输出:

function1 active
function2 active
function3 active
We will call the first function
function4 use the parameter: 10
We will call the second function
function4 use the parameter: 11
We will call the third function
function4 use the parameter: 12

有趣的事实:String(输出)的打印速度比带有print的函数调用要慢...这也是一个警告:不要以这种策略相信序列