【问题标题】:Is there any alternative for NSInvocation in Swift?Swift 中的 NSInvocation 有什么替代方案吗?
【发布时间】:2016-11-10 17:44:32
【问题描述】:

我正在尝试调用具有多个 (2+) 参数的 选择器(可以确定参数的数量)。但是,选择器在编译时是未知的(实际上是用NSSelectorFromString 生成的)。

在 Objective-C 中,我可以创建一个调用并为其设置参数并调用它。但这在 Swift 中不可用。有没有办法解决?喜欢:

let obj = SomeClass()
let selector = NSSelectorFromString("arg1:arg2:arg3:") //selector, arguments known only at runtime
//invoke selector

【问题讨论】:

  • 您要解决的具体问题是什么?
  • 很抱歉,我正在为客户工作。但我会尽量描述它。我必须借助配置文件来配置视图,可以根据需要进行配置。也就是说,可能的配置列表(每个都尝试访问许多不同的方法)很大。是的,它们可以逐案管理(将案例映射到方法),但我正在尝试使用通用解决方案来尝试和管理它。
  • 这种代码动态性通常是不良架构的结果(无意冒犯)。有一些非常具体的例外,但在 95% 的情况下,您不需要使用 NSInvocation 并且您也不应该使用,即使在 Objective-C 中也是如此。一种可能的解决方案是在字典中命名闭包,但即使这样也有点代码味道。
  • @Sulthan 我不会这么说,为什么架构很差?
  • @3000 你不能。问题是方法的名称是一个字符串,因此编译器无法检查是否存在这样的方法。对于使用#selector 语法的选择器,这部分解决了,但NSInvocation 没有这样的语法。闭包更安全。由于 Obj-C 中方法的名称会影响内存管理,因此还有一些其他方面与内存管理相关。总而言之,这是旧的做事方式,我们现在有更好、更安全的替代方案。

标签: ios swift swift3 selector nsinvocation


【解决方案1】:

斯威夫特 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】:

    恐怕在 Swift 中没有办法做到这一点。

    但是,您可能有一个 Objective-C 类来管理您的动态调用。你可以在那里使用NSInvocation

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多