Reflection with Swift - Get functions name of a class
Reflection in Swift is very limited compared to the hackeries available in Objective-C. Having said that, the Objective-C runtime functions are still available in Swift. You can sneak your class into the Objective-C world by making it inherit from NSObject
:
class MyClass: NSObject {
func f1() {}
func f2() {}
func f3() {}
}
let myClass = MyClass.self
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(myClass, &methodCount)
for i in 0..<Int(methodCount) {
let selName = sel_getName(method_getName(methodList[i]))
let methodName = String(CString: selName, encoding: NSUTF8StringEncoding)!
print(methodName)
}
This will give you 4 methods: f1 ... f3
and init
.
(Reflection) Calling A Method With Parameters By Function Name In Swift
First of all, as you noted Swift doesn't have full reflection capabilities and rely on the coexisting ObjC to provide these features.
So even if you can write pure Swift code, you will need Solution
to be a subclass of NSObject
(or implement NSObjectProtocol
).
Playground sample:
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")
}
There are other points of concern here:
- Swift's
perform
is limited to 2 parameters - you need to have the exact signature of the method (#selector here)
If you can stick an array in the first parameters, and alway have the same signature then you're done.
But if you really need to go further you have no choice than to go with ObjC, which doesn't work in Playground.
You could create a Driver.m file of the like:
#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:¶meters[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;
}
Add it to a bridging-header (for Swift to know about what is in ObjC):
// YourProjectName-Bridging-Header.h
id call (NSObject *callOn, NSString *callMethod, NSArray *callParameters);
And call it with a Solution.swift like this:
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"
}
}
output:
Hello solution
Optional(return)
Edit: compilation
To compile both ObjC and Swift on the command line you can first compile ObjC to an object file:
$ cc -O -c YouObjCFile.m
Then compile your Swift project with the bridging header and the object file:
$ swiftc -import-objc-header ../Your-Bridging-Header.h YouObjCFile.o AllYourSwiftFiles.swift -o program
working sample
Swift 5 reflection get list of class properties and call them
From the docs:
Describes the instance methods implemented by a class.
constMethod1
and constMethod2
are computed class properties, which translates to class methods in Objective-C. So they won't be returned by class_copyMethodList
. But fear not, the docs also say:
To get the class methods of a class, use
class_copyMethodList(object_getClass(cls), &count)
.
So you can do:
let methodList = class_copyMethodList(object_getClass(MyClass.self), &methodCount)
To call it, you can use perform
:
if crtMethodStr.hasPrefix("const") {
let result = MyClass.perform(method_getName(unwrapped!))!.takeUnretainedValue()
print(result)
}
Get a Swift class's property name as a String
If you are ok with making your properties @objc you can get the property name like so:
class Person {
@objc var firstName: String
var lastName: String
var downloading: Bool
func excludePropertiesFromCloud() -> [String] {
return [#keyPath(firstName)]
}
}
Related Topics
iOS Swift Didbegincontact Not Being Called
Using @Fetchrequest(Entity: ) for Swiftui MACos App Crashes
How to Subclass a Class Which Doesn't Have Any Designated Initializers
Make a Type Itself -- Not Its Instances -- Conform to a Protocol
How to Cast a Metaclass Object to a Protocol Type in Swift
Building for Arm64E on Apple Silicon
Icloud Drive Issue: "[Documentmanager] Failed to Associate Thumbnails for Picked Url"
How to Create Public Extensions, in a Shared Framework, for Xctest
Problems Accessing Calendar Using Ekeventstore on Osx Sierra with Swift 3
Calling Nsexception.Raise() in Swift
@Objc Redundancy When Having @Objcmembers Private Dynamic Var
Nswindow with Round Corners in Swift
How to Import Modules Without an Xcode Project in Swift
Cllocation Distancefromlocation (In Swift)
Accurately Get a Color from Pixel on Screen and Convert Its Color Space