Call a method with dynamic class name in swift

4k views Asked by At

How can we call class functions with a dynamic class name?

Assume the following example where I have two class with methods with same signature

class Foo{
   class func doSomething()

}

class Foobar {
   class func doSomething()
}

class ActualWork{
   //call following method with a variable type so that it accepts dynamic class  name
   func callDynamicClassMethod(x: dynamicClass)
    x.doSomething() 
  }

How can this be implemented so that x accepts values at run time

Edit: Sorry, I missed to mention that I was looking for any other ways other than protocol oriented approach. This is more of an exploratory question to explore if there is a more direct approach/pods/libraries to achieve this.

5

There are 5 answers

1
Nikita Kurtin On BEST ANSWER

I liked this question, because it made me to think a lit'bit outside of the box.

I'll answer it, by dividing it into a few parts.

First

call class functions

Class function is basically a Type methods, which can be achieved using the static word inside the class context.

Taking that into account, you can get a simple solution, using protocol and passing the class reference (conforming to that protocol) like this:

protocol Aaa{
   static func doSomething();
}
class Foo : Aaa{
  static func doSomething() {
    print("Foo doing something");
  }
}
class FooBar : Aaa{
  static func doSomething() {
    print("FooBar doing something");
  }
}

class ActualWork{

  //Using class (static) method
  func callDynamicClassMethod <T: Aaa> (x: T.Type) {
    x.doSomething();
  }
}


//This is how you can use it
func usage(){
    let aw = ActualWork();

    aw.callDynamicClassMethod(x: Foo.self);
    aw.callDynamicClassMethod(x: Foo.self);
}

Second

In case you don't really need the method on the class context, you may consider using instance methods. In that case the solution would be even simpler, like this:

protocol Bbb{
  func doSomething();
}
class Bar : Bbb{
  func doSomething() {
    print("Bar instance doing something");
  }
}
class BarBar : Bbb{
  func doSomething() {
    print("BarBar instance doing something");
  }
}
class ActualWork{
  //Using instance (non-static) method
  func callDynamicInstanceMethod <T: Bbb> (x: T){
    x.doSomething();
  }
}
//This is how you can use it
func usage(){
   let aw = ActualWork();
    aw.callDynamicInstanceMethod(x: Bar());
    aw.callDynamicInstanceMethod(x: BarBar());
}

Third

If you need to use the class func syntax, as OP originally did:

class func doSomething()

You CANNOT simply use a protocol. Because protocol is not a class... So compiler won't allow it.

Cannot declare class function inside protocol

But it's still possible, you can achieve that by using Selector with NSObject.perform method

like this:

class ActualWork : NSObject{

    func callDynamicClassMethod<T: NSObject>(x: T.Type, methodName: String){
        x.perform(Selector(methodName));
    }

}

class Ccc : NSObject{
    @objc class func doSomething(){
        print("Ccc class Doing something ");
    }

}

class Ddd : NSObject{
    @objc class func doSomething(){
        print("Ccc class Doing something ");
    }

    @objc class func doOther(){
        print("Ccc class Doing something ");
    }
}

//This is how you can use it
func usage() {
    let aw = ActualWork();

    aw.callDynamicClassMethod(x: Ccc.self, methodName: "doSomething");
    aw.callDynamicClassMethod(x: Ddd.self, methodName: "doSomething");
    aw.callDynamicClassMethod(x: Ddd.self, methodName: "doOther");

}
1
Bhavesh.iosDev On

you can achieve this with help of Protocol

 protocol common {
   static func doSomething()
}


class Foo : common{
   static  func doSomething() {
        print("Foo")
    }
}

class Foobar : common {
   static func doSomething() {
        print("Foobar")
    }
}


class ActualWork{
    //call following method with a variable type so that it accepts dynamic class  name
    func callDynamicClassMethod(x: common.Type) {
            x.doSomething()
    }
}

let fooObj : common = Foo()
let Foobarobj : common = Foobar()

let workObk = ActualWork()
workObk.callDynamicClassMethod(x:Foo.self)
workObk.callDynamicClassMethod(x:Foobar.self)
0
SIlvester On

Generics and Protocol oriented programming will do the job:

protocol Doable {

    static func doSomething()
}

class Foo: Doable {

    static func doSomething() {
        debugPrint("Foo")
    }
}

class Foobar: Doable {

    static func doSomething() {
        debugPrint("Foobar")
    }
}

class ActualWork {

    func callDynamicClassMethod<T: Doable>(x: T.Type) {
        x.doSomething()
    }
}

let work = ActualWork()

work.callDynamicClassMethod(x: Foo.self)
work.callDynamicClassMethod(x: Foobar.self)
1
RahmiBozdag On

I think, there are three solutions. I shared an sample below.

  1. Use "protocol" that has "doSomething()" function requirements.
  2. Create a function which gets function definition as a parameter.
  3. Use reflection. you can use EVReflection that is good Api for reflection.

sample code:

protocol FooProtocol {
    static func doSomething()
}

class Foo: FooProtocol {
    class func doSomething() {
        print("Foo:doSomething")
    }
}

class Foobar: FooProtocol {
    class func doSomething() {
        print("Foobar:doSomething")
    }
}

class ActualWork {
    func callDynamicClassMethod<T: FooProtocol>(x: T.Type) {
        x.doSomething()
    }

    func callDynamicClassMethod(x: @autoclosure () -> Void) {
        x()
    }

    func callDynamicClassMethod(x: () -> Void) {
        x()
    }
}

ActualWork().callDynamicClassMethod(x: Foo.self)
ActualWork().callDynamicClassMethod(x: Foobar.self)
print("\n")
ActualWork().callDynamicClassMethod(x: Foo.doSomething())
ActualWork().callDynamicClassMethod(x: Foobar.doSomething())
print("\n")
ActualWork().callDynamicClassMethod(x: Foo.doSomething)
ActualWork().callDynamicClassMethod(x: Foobar.doSomething)
0
Cristik On

Looks like you are searching for duck typing, and this is harder to achieve in a statically typed language (with some exceptions, listed in the linked Wikipedia page).

This is because dynamically calling a method requires knowledge about the layout of the target object, thus either inheritance of the class declaring the method, or conformance to a protocol that requires that method.

Starting with Swift 4.2, and the introduction of dynamic member lookup, there is another approach to solve your problem, however it also involves some ceremony:

// This needs to be used as base of all classes that you want to pass
// as arguments 
@dynamicMemberLookup
class BaseDynamicClass {
    subscript(dynamicMember member: String) -> () -> Void {
        return { /* empty closure do nothing */ }
    }
}

// subclasses can choose to respond to member queries any way they like
class Foo: BaseDynamicClass {
    override subscript(dynamicMember member: String) -> () -> Void {
        if member == "doSomething" { return doSomething }
        return super[dynamicMember: member]
    }

    func doSomething() {
        print("Dynamic from Foo")
    }

}

class Bar: BaseDynamicClass {
    override subscript(dynamicMember member: String) -> () -> Void {
        if member == "doSomething" { return doSomething }
        return super[dynamicMember: member]
    }

    func doSomething() {
        print("Dynamic from Bar")
    }
}

func test(receiver: BaseDynamicClass) {
    receiver.doSomething()
}

test(receiver: Bar()) // Dynamic from Bar

To conclude, in the current Swift version there is no way to have both the argument and the method dynamic, some common ground needs to be set.