【问题标题】:Alternative to performSelector in Swift?在 Swift 中替代 performSelector?
【发布时间】:2014-08-01 05:53:06
【问题描述】:

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

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

【问题讨论】:

  • 重新考虑您的方法。在我的代码中,一个闭包解决了它。
  • 你仍然可以这样做,在这里找到我的答案stackoverflow.com/questions/24126261/…
  • @matt 很好,那么请为我重新考虑末日金字塔,谢谢。
  • @turingtested 我将其重新考虑为一系列guard 语句。 :)

标签: swift performselector


【解决方案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 配合使用(因此performSelector: 的ARC 警告)。

【讨论】:

  • 看起来不错,但您可以像这样重写您的 selectorClosure 属性(注意 nil 对可选的赋值是隐式的):var selectorClosure: (() -> Void)?
【解决方案2】:

我也在为此苦苦挣扎。我终于意识到我不需要使用目标或选择器。对我来说,解决方案是将 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();

【讨论】:

  • 这对我有用。我需要带有一个参数的函数,所以我声明了这样的“选择器”属性 --- var tapFunction: ((sender: AnyObject?) -> ())? --- 然后声明一个函数为 --- func actionMethod(sender: AnyObject?)
【解决方案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!

【讨论】:

  • 谢谢,看来 A (dispatch_after(DISPATCH_TIME_NOW, ...) 是使用 Swift 参数动态调用 ObjC 方法的唯一方法。
  • 顺便说一句,真的有必要在主队列上调度吗? IE。当您更改 UIView 的属性时,它可能会通知观察者等待主线程事件?此外,当您在主线程上调度时,它无论如何都会在不同的线程上执行。
【解决方案4】:

我正在使用以下解决方案:

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

【讨论】:

  • 问题不是延迟
【解决方案5】:

你可以在 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 所执行的操作

【讨论】:

  • 这并不完全相同,因为 1) 它是异步执行的,而 performSelector:(没有 afterDelay)是同步的; 2)它只适用于我们正在运行运行循环的线程。
  • 嗨 @user102008 它对我来说很好用,现在,如果我将重复设置为 true,如何停止计时器?
  • @Josh timer.invalidate()
【解决方案6】:

我有一种情况,选择器是用来自 plist 文件的字符串文字构造的。 因此,用下一个代码解决了在 swift 中执行某些选择器的最快方法

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

【讨论】:

    【解决方案7】:

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

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

    【讨论】:

      【解决方案8】:

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

      【讨论】:

      • 您的答案通常仅限于选择器,如果它们在 swift 中仍然需要。但问题是关于 performSelector 的替代品,它用于异步内容、延迟、等待完成或简单快速的线程控制(在主线程或后台线程上运行)。
      • 感谢这个例子,这是我发现的第一个例子,它概述了你如何在 Swift 中调用具有已知签名的“未知”方法(如果我用动态建模事物,这有时是我想做的方法)。比NSInvocation 更有用,而且更不容易出错。
      【解决方案9】:

      有时(特别是如果您使用target/action 模式)您可能必须使用方法-[UIApplication sendAction:to:from:forEvent:](适用于iOS),因此在Swift 中可能是这样的:

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

      【讨论】:

        【解决方案10】:

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

        【讨论】:

        • @RoiMulia 是的,只要你用 Xcode 7+ 编译
        【解决方案11】:

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

        【讨论】:

        • 嘿..我刚刚看到火箭了吗..!!! :) 开个玩笑.. 就像一个魅力.. 谢谢。
        • 是的...谢谢。
        【解决方案12】:

        “Matej Ukmar's”评论“J Terry's”答案的真实世界示例:

        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
        

        【讨论】:

          【解决方案13】:

          我不知道从什么时候开始,但 Apple 在 Xcode 7.1.1 中带回了 performSelector(至少这是我正在使用的版本)。

          在我目前正在构建的应用程序中,我在从 CoreAnimator 生成的 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)
          

          【讨论】:

            【解决方案14】:

            斯威夫特 3

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

            【讨论】:

              【解决方案15】:

              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)
              

              正如 iOS_MIB in https://stackoverflow.com/a/48644264/5329717不等同于

              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
              • 对于对象,如果其符号可用,则使用 Any 或精确类类型
              • 对于值类型,请使用相关类型
              • 对于 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)
                  }
              }
              

              【讨论】:

                【解决方案16】:

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

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

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

                这是我测试语法和行为的游乐场的一个快速示例......(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
                

                有趣的事实:字符串的打印(什么)比使用打印的函数调用要慢...这也是一个警告:不要相信这种策略的顺序

                【讨论】:

                  猜你喜欢
                  • 2014-07-30
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-08-04
                  • 1970-01-01
                  • 1970-01-01
                  • 2023-04-04
                  • 1970-01-01
                  相关资源
                  最近更新 更多