ios - (反射(reflection))在 Swift 中按函数名调用带参数的方法

标签 ios swift reflection

语境

我有一个名为 Solution 的类的实例我有一个函数名作为字符串 functionName我想调用Solution实例 solutionInstance .我在数组中有函数的参数,我也想传递这些参数。

我正在使用 Swift 编译器来编译我所有的 .swift文件一起(swiftc 与枚举文件,然后 -o 和输出文件名)然后我运行最终输出。

Python 示例

这是我在 Python 中执行此操作的方法:

method = getattr(solutionInstance, functionName) # get method off of instance for function
programOutput = method(*testInputsParsed) # pass the list of parameters & call the method

目的

这是在容器中运行以运行用户代码的服务器端代码。此代码位于“驱动程序”main.swift 中调用方法和编排测试的文件。

问题

Swift 是静态类型的,我一直在搜索,大多数消息来源说 Swift 中的反射支持有限(并建议“进入 Objective-C”以获得所需的功能)。

Swift 不是我的母语(TypeScript/JavaScript、Java、Python 最强,然后是 C# 和 C++ 温和,然后只是为这个功能实现 Swift 代码)所以我不确定这意味着什么,我还没有找到一个确定的答案。

问题

如何在 Solution 上通过函数名称调用函数类实例(它没有实现任何协议(protocol),至少由我实现)并在 Swift 中传递参数数组(使用反射)?我的设置需要如何更改才能实现(导入库等)

谢谢!

引用帖子
  • Calling Method using reflection
  • Does Swift support reflection?
  • Call a method from a String in Swift
  • How to invoke a class method using performSelector() on AnyClass in Swift?
  • Dynamically call a function in Swift
  • 最佳答案

    首先,正如您所指出的,Swift 没有完整的反射功能,并且依赖于并存的 ObjC 来提供这些功能。

    所以即使你可以编写纯 Swift 代码,你也需要 Solution成为 NSObject 的子类(或实现 NSObjectProtocol )。

    游乐场样本:

    class Solution: NSObject {
    
        @objc func functionName(greeting: String, name: String) {
            print(greeting, name)
        }
    
    }
    
    let solutionInstance = Solution() as NSObject
    let selector = #selector(Solution.functionName)
    if solutionInstance.responds(to: selector) {
        solutionInstance.perform(selector, with: "Hello", with: "solution")
    }
    

    这里还有其他值得关注的点:
  • swift 的perform仅限于 2 个参数
  • 您需要拥有该方法的确切签名(此处为#selector)

  • 如果您可以在第一个参数中添加一个数组,并且始终具有相同的签名,那么您就完成了。
    但是如果你真的需要更进一步,你别无选择,只能使用 ObjC,它在 Playground 中不起作用。

    您可以创建类似的 Driver.m 文件:

    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    
    id call (NSObject *callOn, NSString *callMethod, NSArray <NSObject *>*callParameters)
    {
        void *result = NULL;
        unsigned int index, count;
    
        Method *methods = class_copyMethodList(callOn.class, &count);
        for (index = 0; index < count; ++index)
        {
            Method method = methods[index];
    
            struct objc_method_description *description = method_getDescription(method);
            NSString *name = [NSString stringWithUTF8String:sel_getName(description->name)];
            if ([name isEqualToString:callMethod])
            {
                NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:description->types];
                NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    
                NSObject *parameters[callParameters.count];
                for (int p = 0; p < callParameters.count; ++p) {
                    parameters[p] = [callParameters objectAtIndex:p];
                    [invocation setArgument:&parameters[p] atIndex:p + 2]; // 0 is self 1 is SEL
                }
                [invocation setTarget:callOn];
                [invocation setSelector:description->name];
                [invocation invoke];
                [invocation getReturnValue:&result];
                break;
            }
        }
        free(methods);
    
        return (__bridge id)result;
    }
    

    将其添加到桥接头(让 Swift 了解 ObjC 中的内容):
    // YourProjectName-Bridging-Header.h
    id call (NSObject *callOn, NSString *callMethod, NSArray *callParameters);
    

    并用这样的 Solution.swift 调用它:

    import Foundation
    
    class Solution: NSObject {
    
        override init() {
            super.init()
            // this should go in Driver.swift
            let result = call(self, "functionNameWithGreeting:name:", ["Hello", "solution"])
            print(result as Any)
        }
    
        @objc
        func functionName(greeting: String, name: String) -> String {
            print(greeting, name)
            return "return"
        }
    
    }
    

    输出:
    Hello solution
    Optional(return)
    

    编辑:编译

    要在命令行上同时编译 ObjC 和 Swift,您可以首先将 ObjC 编译为目标文件:
    $ cc -O -c YouObjCFile.m
    

    然后使用桥接头和目标文件编译您的 Swift 项目:
    $ swiftc -import-objc-header ../Your-Bridging-Header.h YouObjCFile.o AllYourSwiftFiles.swift -o program
    

    working sample

    关于ios - (反射(reflection))在 Swift 中按函数名调用带参数的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61718880/

    相关文章:

    ios - iOS 下载应用程序时的通知

    ios - 如何检测功能中的手指位置

    ios - 带身份验证的 Swift Telnet 流

    Java反射使 final方法成为非 final方法

    scala - 如何生成静态成员并将其添加到类型宏中的类?

    java - 如何使用 Guava 从任何方法获取返回类型?

    ios - 更改 UISearchBar 的文本颜色和边框颜色

    ios - UILabel:adjustsFontSizeToFitWidth 不起作用

    ios - 是否可以使用CoreLocation/MapKit查找纬度/经度附近的地址?

    ios - 如何快速添加从我的 appdelegate 类调用的弹出窗口?