Swift:如何通过Objective-C Runtime获取方法列表、参数名及类型
如何用Objective-C Runtime获取@objc扩展方法的参数名和类型
嘿,这个问题我之前折腾过好一会儿,刚好能给你指条明路!你现在用class_copyMethodList和method_getName只能拿到类似method3WithArg1:arg2:的选择器名称,确实没法直接得到#function那种带完整参数名的签名,不过咱们可以结合Runtime的其他API来解析出需要的信息。
核心思路
Objective-C Runtime里的Method对象包含了方法的所有元数据,我们需要:
- 遍历目标类的所有方法(实例方法用
class_copyMethodList,类方法用class_copyClassMethodList) - 从
Method中提取选择器(SEL)和类型编码(Type Encoding) - 解析选择器名称来拆分出参数名(适配Swift @objc方法的命名转换规则)
- 解析类型编码来获取参数和返回值的类型信息
代码实现示例
先定义一个带@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
相关产品推荐
相关产品推荐

