替代Swift中的performSelector?

use*_*008 46 performselector swift

Swift中没有这类performSelector方法.那么如何在一个对象上调用一个方法,在运行时选择要调用的方法,而在编译时不知道?显然在Swift中也没有.@objcNSInvocation

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

Sul*_*han 18

使用闭包

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

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

var a = A()
a.selectorClosure = { println("Selector called") }
a.invoke()
Run Code Online (Sandbox Code Playgroud)

请注意,这并不是什么新鲜事,即使在Obj-C中,新的API更喜欢使用块performSelector(比较UIAlertView使用respondsToSelector:performSelector:调用委托方法,使用新的UIAlertController).

使用performSelector:总是不安全,并且不能很好地与ARC一起使用(因此ARC警告performSelector:).

  • 看起来不错,但是你可以像这样重写你的`selectorClosure`属性(注意`可选的`nil`赋值是隐含的):`var selectorClosure:(() - > Void)?` (6认同)

Fiz*_*uzz 15

从Xcode 7开始,完整的performSelector方法系列在Swift中可用,包括performSelectorOnMainThread()performSelectorInBackground().请享用!


vla*_*f81 12

方法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)")
}
Run Code Online (Sandbox Code Playgroud)

控制台日志:

问候世界

附着物:阳光

方法B.

之前发现了这个替代方案,我也在设备和模拟器上进行了测试.我的想法是使用以下UIControl方法:

func sendAction(_ action: Selector, to target: AnyObject!, forEvent event: UIEvent!)
Run Code Online (Sandbox Code Playgroud)

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")
}
Run Code Online (Sandbox Code Playgroud)

控制台日志:

问候世界

方法C.

的NSTimer

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


Mag*_*goo 9

斯威夫特3

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


Mat*_*mar 7

根据@JTerry的回答"你不需要Swift中的选择器",你可以为变量分配实际的方法.我的解决方案如下(我在方法中需要一个参数):

class SettingsMenuItem: NSObject {
    ...
    var tapFunction: ((sender: AnyObject?) -> ())?
}
Run Code Online (Sandbox Code Playgroud)

然后在视图控制器中我以这种方式声明,分配和运行该函数:

class SettingsViewController: UITableViewController {

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

    ...

    menuItem.tapFunction = editProfile

    ...

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


}
Run Code Online (Sandbox Code Playgroud)


小智 6

你可以在Swift中使用它

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


func someSelector() {
// Something after a delay
}
Run Code Online (Sandbox Code Playgroud)

通过这个,您可以执行Objective-C中的performSelector执行的操作

  • 这不完全相同,因为1)它是异步的,而`performSelector:`(没有`afterDelay`)是同步的; 2)它只适用于我们运行运行循环的线程. (7认同)

Kam*_*l.S 6

Swift 3.1
对于标准的Swift项目,闭包是Sulthan的答案中已经涵盖的优雅解决方案。如果选择器字符串名称取决于旧的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)
Run Code Online (Sandbox Code Playgroud)

还具有主线程变体:

instance.performSelector(onMainThread: NSSelectorFromString("selectorArg:"), with: arg, waitUntilDone: false)
Run Code Online (Sandbox Code Playgroud)

iOS_MIB /sf/answers/3405098511/中所述,不等同

DispatchQueue.main.async {
   //perform selector
}
Run Code Online (Sandbox Code Playgroud)

和后台线程变体:

instance.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)
Run Code Online (Sandbox Code Playgroud)

但是有一些限制:

  • 它只能接受0-2个参数
  • 整数和选择器之类的值类型参数不起作用
  • 无法处理返回的值类型
  • 返回对象为 Unmanaged<AnyObject>

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

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

@convention(c)不是所有的类型都允许

  • 对于类,请使用Any或AnyClass
  • 对于对象,请使用任何符号或确切的类类型(如果其符号可用)
  • 对于值类型,请使用相关类型
  • 对于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) 
Run Code Online (Sandbox Code Playgroud)

这些是静态等效项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)
Run Code Online (Sandbox Code Playgroud)

局限性:

  • 前面提到的所有类型问题
  • 接收器类需要具有定义的符号

获取运行时静态方法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
Run Code Online (Sandbox Code Playgroud)

没有实际的理由,但是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)
Run Code Online (Sandbox Code Playgroud)

相同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)
    }
}
Run Code Online (Sandbox Code Playgroud)