Swift 中是否有 NSInvocation 的替代方案?
Is there any alternative for NSInvocation in Swift?
我正在尝试使用多个 (2+) 参数调用 选择器(可以确定参数的数量)。但是,选择器在编译时是未知的(实际上是用 NSSelectorFromString
生成的)。
在 Objective-C 中,我可以创建一个调用并为其设置参数并调用它。但这在 Swift 中不可用。有没有办法解决?喜欢:
let obj = SomeClass()
let selector = NSSelectorFromString("arg1:arg2:arg3:") //selector, arguments known only at runtime
//invoke selector
恐怕 Swift 中没有办法做到这一点。
但是,您可能有一个 Objective-C class 来管理您的动态调用。您可以在那里使用 NSInvocation
。
Swift 3.1
NSInvocation
可以动态使用,但只能作为一个有趣的练习,绝对不适合严肃的应用。还有更好的alternatives.
import Foundation
class Test: NSObject {
@objc var name: String? {
didSet {
NSLog("didSetCalled")
}
}
func invocationTest() {
// This is the selector we want our Invocation to send
let namePropertySetterSelector = #selector(setter:name)
// Look up a bunch of methods/impls on NSInvocation
let nsInvocationClass: AnyClass = NSClassFromString("NSInvocation")!
// Look up the "invocationWithMethodSignature:" method
let nsInvocationInitializer = unsafeBitCast(
method_getImplementation(
class_getClassMethod(nsInvocationClass, NSSelectorFromString("invocationWithMethodSignature:"))!
),
to: (@convention(c) (AnyClass?, Selector, Any?) -> Any).self
)
// Look up the "setSelector:" method
let nsInvocationSetSelector = unsafeBitCast(
class_getMethodImplementation(nsInvocationClass, NSSelectorFromString("setSelector:")),
to:(@convention(c) (Any, Selector, Selector) -> Void).self
)
// Look up the "setArgument:atIndex:" method
let nsInvocationSetArgAtIndex = unsafeBitCast(
class_getMethodImplementation(nsInvocationClass, NSSelectorFromString("setArgument:atIndex:")),
to:(@convention(c)(Any, Selector, OpaquePointer, NSInteger) -> Void).self
)
// Get the method signiture for our the setter method for our "name" property.
let methodSignatureForSelector = NSSelectorFromString("methodSignatureForSelector:")
let getMethodSigniatureForSelector = unsafeBitCast(
method(for: methodSignatureForSelector)!,
to: (@convention(c) (Any?, Selector, Selector) -> Any).self
)
// ObjC:
// 1. NSMethodSignature *mySignature = [self methodSignatureForSelector: @selector(setName:)];
// 2. NSInvocation *myInvocation = [NSInvocation invocationWithMethodSignature: mySignature];
// 3. [myInvocation setSelector: @selector(setName:)];
// 4. [myInvocation setArgument: @"new name", atIndex: 2];
// 5. [myInvocation invokeWithTarget: self];
// 1.
let namyPropertyMethodSigniature = getMethodSigniatureForSelector(self, methodSignatureForSelector, namePropertySetterSelector)
// 2.
let invocation = nsInvocationInitializer(
nsInvocationClass,
NSSelectorFromString("invocationWithMethodSignature:"),
namyPropertyMethodSigniature
) as! NSObject // Really it's an NSInvocation, but that can't be expressed in Swift.
// 3.
nsInvocationSetSelector(
invocation,
NSSelectorFromString("setSelector:"),
namePropertySetterSelector
)
var localName = "New name" as NSString
// 4.
withUnsafePointer(to: &localName) { stringPointer in
nsInvocationSetArgAtIndex(
invocation,
NSSelectorFromString("setArgument:atIndex:"),
OpaquePointer(stringPointer),
2
)
}
// 5.
invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
}
}
let object = Test()
object.invocationTest()
我正在尝试使用多个 (2+) 参数调用 选择器(可以确定参数的数量)。但是,选择器在编译时是未知的(实际上是用 NSSelectorFromString
生成的)。
在 Objective-C 中,我可以创建一个调用并为其设置参数并调用它。但这在 Swift 中不可用。有没有办法解决?喜欢:
let obj = SomeClass()
let selector = NSSelectorFromString("arg1:arg2:arg3:") //selector, arguments known only at runtime
//invoke selector
恐怕 Swift 中没有办法做到这一点。
但是,您可能有一个 Objective-C class 来管理您的动态调用。您可以在那里使用 NSInvocation
。
Swift 3.1
NSInvocation
可以动态使用,但只能作为一个有趣的练习,绝对不适合严肃的应用。还有更好的alternatives.
import Foundation
class Test: NSObject {
@objc var name: String? {
didSet {
NSLog("didSetCalled")
}
}
func invocationTest() {
// This is the selector we want our Invocation to send
let namePropertySetterSelector = #selector(setter:name)
// Look up a bunch of methods/impls on NSInvocation
let nsInvocationClass: AnyClass = NSClassFromString("NSInvocation")!
// Look up the "invocationWithMethodSignature:" method
let nsInvocationInitializer = unsafeBitCast(
method_getImplementation(
class_getClassMethod(nsInvocationClass, NSSelectorFromString("invocationWithMethodSignature:"))!
),
to: (@convention(c) (AnyClass?, Selector, Any?) -> Any).self
)
// Look up the "setSelector:" method
let nsInvocationSetSelector = unsafeBitCast(
class_getMethodImplementation(nsInvocationClass, NSSelectorFromString("setSelector:")),
to:(@convention(c) (Any, Selector, Selector) -> Void).self
)
// Look up the "setArgument:atIndex:" method
let nsInvocationSetArgAtIndex = unsafeBitCast(
class_getMethodImplementation(nsInvocationClass, NSSelectorFromString("setArgument:atIndex:")),
to:(@convention(c)(Any, Selector, OpaquePointer, NSInteger) -> Void).self
)
// Get the method signiture for our the setter method for our "name" property.
let methodSignatureForSelector = NSSelectorFromString("methodSignatureForSelector:")
let getMethodSigniatureForSelector = unsafeBitCast(
method(for: methodSignatureForSelector)!,
to: (@convention(c) (Any?, Selector, Selector) -> Any).self
)
// ObjC:
// 1. NSMethodSignature *mySignature = [self methodSignatureForSelector: @selector(setName:)];
// 2. NSInvocation *myInvocation = [NSInvocation invocationWithMethodSignature: mySignature];
// 3. [myInvocation setSelector: @selector(setName:)];
// 4. [myInvocation setArgument: @"new name", atIndex: 2];
// 5. [myInvocation invokeWithTarget: self];
// 1.
let namyPropertyMethodSigniature = getMethodSigniatureForSelector(self, methodSignatureForSelector, namePropertySetterSelector)
// 2.
let invocation = nsInvocationInitializer(
nsInvocationClass,
NSSelectorFromString("invocationWithMethodSignature:"),
namyPropertyMethodSigniature
) as! NSObject // Really it's an NSInvocation, but that can't be expressed in Swift.
// 3.
nsInvocationSetSelector(
invocation,
NSSelectorFromString("setSelector:"),
namePropertySetterSelector
)
var localName = "New name" as NSString
// 4.
withUnsafePointer(to: &localName) { stringPointer in
nsInvocationSetArgAtIndex(
invocation,
NSSelectorFromString("setArgument:atIndex:"),
OpaquePointer(stringPointer),
2
)
}
// 5.
invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
}
}
let object = Test()
object.invocationTest()