首页 > 解决方案 > (反思)在 Swift 中按函数名调用带参数的方法

问题描述

语境

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

谢谢!

引用的帖子

标签: iosswiftreflection

解决方案


首先,正如您所指出的,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")
}

这里还有其他值得关注的点:

  • Swiftperform仅限于 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

工作样本


推荐阅读