You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift:如何通过Objective-C Runtime获取方法列表、参数名及类型

如何用Objective-C Runtime获取@objc扩展方法的参数名和类型

嘿,这个问题我之前折腾过好一会儿,刚好能给你指条明路!你现在用class_copyMethodList和method_getName只能拿到类似method3WithArg1:arg2:的选择器名称,确实没法直接得到#function那种带完整参数名的签名,不过咱们可以结合Runtime的其他API来解析出需要的信息。

核心思路

Objective-C Runtime里的Method对象包含了方法的所有元数据,我们需要:

  1. 遍历目标类的所有方法(实例方法用class_copyMethodList,类方法用class_copyClassMethodList)
  2. 从Method中提取选择器(SEL)和类型编码(Type Encoding)
  3. 解析选择器名称来拆分出参数名(适配Swift @objc方法的命名转换规则)
  4. 解析类型编码来获取参数和返回值的类型信息

代码实现示例

先定义一个带@objc扩展的测试类:

class MyClass: NSObject {}

@objc extension MyClass {
    func method1() { print(#function, type(of: method1)) }
    func method2(arg: String) { print(#function, type(of: method2)) }
    func method3(arg1: Int, arg2: Bool) -> String {
        print(#function, type(of: method3))
        return "result"
    }
}

然后写一个工具函数来提取方法详情:

import Foundation
import ObjectiveC

func getMethodDetails(for cls: AnyClass) -> [[String: Any]] {
    var methodDetails = [[String: Any]]()
    var methodCount: UInt32 = 0
    // 获取实例方法列表,类方法用class_copyClassMethodList
    let methods = class_copyMethodList(cls, &methodCount)
    defer { free(methods) } // 记得释放内存,避免泄漏
    
    guard let methodList = methods else { return methodDetails }
    
    for i in 0..<Int(methodCount) {
        let method = methodList[i]
        
        // 1. 获取选择器和完整方法名
        let sel = method_getName(method)
        let fullSelectorName = String(cString: sel_getName(sel))
        
        // 2. 从选择器拆分参数名(适配Swift @objc的命名转换)
        let nameComponents = fullSelectorName.components(separatedBy: ":")
        let baseMethodName = nameComponents.first ?? ""
        var paramNames = [String]()
        
        if nameComponents.count > 1 {
            // 去掉最后一个空元素(因为选择器以冒号结尾)
            let paramParts = nameComponents.dropFirst().dropLast()
            paramNames = paramParts.map { part in
                var cleanedName = part
                // 把首字母大写的前缀转成小写,比如"WithArg1" → "arg1"
                if let firstChar = cleanedName.first, firstChar.isUppercase {
                    cleanedName = cleanedName.prefix(1).lowercased() + cleanedName.dropFirst()
                }
                // 处理类似"AtIndex"这样的前缀,转成"index"
                if let atRange = cleanedName.range(of: "At", options: .caseInsensitive) {
                    cleanedName = String(cleanedName[atRange.upperBound...])
                    if let first = cleanedName.first {
                        cleanedName = first.lowercased() + cleanedName.dropFirst()
                    }
                }
                return cleanedName
            }
        }
        
        // 3. 解析类型编码,获取参数和返回值类型
        let typeEncoding = String(cString: method_getTypeEncoding(method))
        let typeChars = Array(typeEncoding)
        var paramTypeEncodings = [String]()
        var index = 0
        
        // 先跳过返回值的类型编码
        while index < typeChars.count {
            let char = typeChars[index]
            // 处理基础类型、对象类型、选择器等
            if char == "@" || char == "#" || char == ":" || char.isLetter {
                index += 1
                if char == "@" || char == "#" {
                    // 跳过对象类型的引号,比如@"NSString"
                    while index < typeChars.count && typeChars[index] != "\"" {
                        index += 1
                    }
                    index += 1
                }
            } else if char.isNumber {
                index += 1
            } else if char == "(" {
                // 处理结构体等复杂类型,跳过括号内的内容
                var depth = 1
                index += 1
                while index < typeChars.count && depth > 0 {
                    if typeChars[index] == "(" { depth += 1 }
                    else if typeChars[index] == ")" { depth -= 1 }
                    index += 1
                }
            } else {
                index += 1
            }
            
            // 跳过前两个参数(self和_cmd,这是Objective-C方法的隐式参数)
            if paramTypeEncodings.count < 2 {
                paramTypeEncodings.append(String(typeChars[index-1..<index]))
                continue
            }
            // 收集实际业务参数的类型编码
            paramTypeEncodings.append(String(typeChars[index-1..<index]))
        }
        
        // 移除隐式参数的类型编码,只保留业务参数
        let actualParamTypes = Array(paramTypeEncodings.dropFirst(2))
        // 提取返回值类型编码
        let returnType = String(typeChars[0..<index])
        
        // 组装最终的方法详情字典
        let detail: [String: Any] = [
            "baseMethodName": baseMethodName,
            "fullSelectorName": fullSelectorName,
            "parameterNames": paramNames,
            "parameterTypeEncodings": actualParamTypes,
            "returnTypeEncoding": returnType
        ]
        methodDetails.append(detail)
    }
    
    return methodDetails
}

// 调用示例
let methodDetails = getMethodDetails(for: MyClass.self)
for detail in methodDetails {
    print("方法名: \(detail["baseMethodName"] ?? "未知")")
    print("完整选择器: \(detail["fullSelectorName"] ?? "未知")")
    print("参数名: \(detail["parameterNames"] ?? [])")
    print("参数类型编码: \(detail["parameterTypeEncodings"] ?? [])")
    print("返回值类型编码: \(detail["returnTypeEncoding"] ?? "未知")")
    print("---")
}

关键说明

  • 参数名解析:Swift的@objc方法会把参数名转换成Objective-C风格的命名(比如method3(arg1:arg2:)变成method3WithArg1:arg2:),所以我们需要对选择器名称做反向转换,提取出原始的Swift参数名。
  • 类型编码解析:method_getTypeEncoding返回的编码遵循Objective-C的类型编码规则,比如i表示Int,@"NSString"表示NSString对象,我们需要解析这些编码来获取类型信息。
  • 类方法处理:如果要获取类方法,只需要把class_copyMethodList换成class_copyClassMethodList即可。

关于#function的完整签名

如果想要完全复刻#function输出的Swift风格签名(比如method3(arg1:arg2:)),用纯Objective-C Runtime做不到——因为#function是Swift编译期的宏,直接嵌入了原始方法签名,而Objective-C Runtime拿到的是桥接后的Objective-C风格信息。如果是调试场景,可以考虑使用Swift的私有Runtime API,但这些API不稳定,不建议在生产环境使用。

内容的提问来源于stack exchange,提问作者big

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:45:53