5

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.

Vkharb
  • 856
  • 11
  • 19
  • 1
    I would try to add a protocol to the Foo and Foobar with doSomething(). Then make a genericType the requires that protocol - then use the genericType as parameter. – Yedy Feb 06 '19 at 10:17

5 Answers5

7

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)
SIlvester
  • 760
  • 5
  • 13
7

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");

}
Gal Yedidovich
  • 757
  • 7
  • 16
Nikita Kurtin
  • 5,889
  • 4
  • 44
  • 48
3

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)
Bhavesh.iosDev
  • 924
  • 9
  • 27
3

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)
RahmiBozdag
  • 138
  • 6
0

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.

Cristik
  • 30,989
  • 25
  • 91
  • 127