performSelector
系列方法在Swift中不可用。那么,如果要在运行时选择要调用的方法而不是在编译时知道该如何调用一个@objc
对象上的方法,该怎么办?显然,在Swift中也无法使用NSInvocation
。我知道在Swift中,您可以将任何方法(只要有一个可见的
@objc
方法声明)发送到类型AnyObject
,类似于Objective-C中的id
。但是,这仍然需要您在编译时硬编码方法名。是否有一种方法可以在运行时动态选择它?performSelector
系列方法在Swift中不可用。那么,如果要在运行时选择要调用的方法而不是在编译时知道该如何调用一个@objc
对象上的方法,该怎么办?显然,在Swift中也无法使用NSInvocation
。@objc
方法声明)发送到类型AnyObject
,类似于Objective-C中的id
。但是,这仍然需要您在编译时硬编码方法名。是否有一种方法可以在运行时动态选择它?使用闭包
class A {
var selectorClosure: (() -> Void)?
func invoke() {
self.selectorClosure?()
}
}
var a = A()
a.selectorClosure = { println("Selector called") }
a.invoke()
注意,即使在 Obj-C 中,新的 API 更喜欢使用块而不是 performSelector
(比较使用 respondsToSelector:
和 performSelector:
来调用委托方法的 UIAlertView
,与新的 UIAlertController
)。始终使用 performSelector:
是不安全的,并且与 ARC 不兼容(因此有关 performSelector:
的 ARC 警告)。selectorClosure
属性(请注意,对可选项进行的空值分配是隐式的):var selectorClosure: (() -> Void)?
- Jack Lawrence从Xcode 7开始,完整的performSelector方法家族都可以在Swift中使用,包括performSelectorOnMainThread()
和performSelectorInBackground()
。享受吧!
使用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)")
}
控制台日志:
问候世界
附加对象:阳光
这个替代方案早些时候已经被发现,我也在设备和模拟器上进行了测试。想法是使用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")
}
控制台日志:
问候世界
NSTimer
class func scheduledTimerWithTimeInterval(_ seconds: NSTimeInterval,
target target: AnyObject!,
selector aSelector: Selector,
userInfo userInfo: AnyObject!,
repeats repeats: Bool) -> NSTimer!
dispatch_after(DISPATCH_TIME_NOW, ...
) 是从 Swift 动态调用带参数的 ObjC 方法的唯一方法。 - CfrSwift 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在https://stackoverflow.com/a/48644264/5329717中指出的那样,这与不相等
DispatchQueue.main.async {
//perform selector
}
和后台线程变体:
instance.performSelector(inBackground: NSSelectorFromString("selectorArg:"), with: arg)
但是,它有一些限制:
Unmanaged<AnyObject>
返回因此,当不需要返回结果和值类型参数时,这种低成本方法非常方便。
获取NSObject
运行时方法IMP
允许使用正确的参数和返回类型进行类型化调用。
@convention(c)(types)->type
允许将IMP
结果转换为兼容的Swift闭包函数。
在@convention(c)
中,并不是所有类型都被允许
这是根据定义不安全的,如果操作不正确,会导致崩溃和副作用。
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)
}
}
Swift 3
perform(#selector(someSelector), with: nil, afterDelay: 1.0, inModes: [.commonModes])
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)
}
}
NSInvocation
更有用,也更少出错。 - Patruvar timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: Selector("someSelector"), userInfo: nil, repeats: false)
func someSelector() {
// Something after a delay
}
performSelector:
(不带afterDelay
)是同步执行的;2)它只在我们正在运行run loop的线程上工作。 - user102008timer.invalidate()
- Hadi Sharghi我也曾为此苦恼。最终我意识到,我不需要使用目标或选择器。对我来说,解决方案是将函数分配给变量并调用该变量。即使从其他类中调用它,它也可以正常工作。以下是一个快速示例:
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();
符号即可。
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)
}
}
target/action
模式时),您可能需要使用-[UIApplication sendAction:to:from:forEvent:]
方法(适用于iOS),所以在Swift中,可以像这样编写代码:UIApplication.sharedApplication()
.sendAction(someSelector, to: someObject, from: antotherObject, forEvent: someEvent)