1

I'm trying to achieve the following but am running into issues :-)

  • create a protocol that UIViewController and UIView subclass can adopt which contain one static method to be called on this class (call it configuration
  • I then want to use the objectiveC runtime to find the classes that adopt this protocol
  • On each of those class I want to call the configuration method
  • The configuration method is to return a dictionary (key: a description string, value: a selector to be called on the class)

So far I was able to create the protocol, find the class implementing the protocol but i'm running into compiling issues.

Here is the protocol

@objc public protocol MazeProtocol: NSObjectProtocol{
   @objc static func configurations() -> NSDictionary
}

Here is the extension to adopt the protocol on one of my class:

extension MapCoordinatorViewController: MazeProtocol {

static func configurations() -> NSDictionary {
    let returnValue = NSMutableDictionary()
    returnValue.setObject(#selector(test), forKey: "test" as NSString)
    return returnValue
}

@objc static func test() {

    print("test")
}}

and here is the code i'm using to try to call the selector returned from the configuration method:

let selectorKey = controllerClass.configurations().allKeys[indexPath.row]
let selector = controllerClass.configurations().object(forKey: selectorKey)
controllerClass.performSelector(selector)        <================ error here

ControllerClass is declared as let controllerClass: MazeProtocol.Type

I get the following compile warning: Instance member 'performSelector' cannot be used on type 'MazeProtocol'

What am I missing?

otusweb
  • 1,638
  • 1
  • 19
  • 30
  • `controllerClass.performSelector` is the main issue, a type of `MazeProtocol` has no method `performSelector` – Scriptable Nov 27 '18 at 15:07
  • 2
    This is Objective-C code using Swift syntax. Why not write this code using Swift features? Use a closure instead of a selector. Use Swift data types instead of Objective-C data types. – rmaddy Nov 27 '18 at 15:30

1 Answers1

4

You can technically force this to work. Please don't. This is horrible Swift. To get this to work, you have to undermine everything Swift is trying to do. But yes, with warnings, you can technically get this to compile and work. Please, please don't.

First, you need to make selector be a Selector. You're using an NSDictionary, which is terrible in Swift, and so you get Any? back. But, yes, you can as! cast it into what you want:

let selector = controllerClass.configurations().object(forKey: selectorKey) as! Selector

And then, defying all the type gods, you can just declare that classes are actually NSObjectProtocol, because why not?

(controllerClass as! NSObjectProtocol).perform(selector)

This will throw a warning "Cast from 'MapCoordinatorViewController.Type' to unrelated type 'NSObjectProtocol' always fails", but it will in fact succeed.

After all that "don't do this," how should you do this? With closures.

public protocol MazeProtocol {
    static var configurations: [String: () -> Void] { get }
}

class MapCoordinatorViewController: UIViewController {}

extension MapCoordinatorViewController: MazeProtocol {

    static let configurations: [String: () -> Void] = [
        "test": test
    ]
    static func test() {
        print("test")
    }
}


let controllerClass = MapCoordinatorViewController.self
let method = controllerClass.configurations["test"]!
method()
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    Thanks Rob, I was so set on the protocol part that I did not think about a swifty solution. – otusweb Nov 28 '18 at 07:31
  • I just tried this. My problem is that I'm using the objectiveC runtime to find which class are adopting the MazeProtocol. So MazeProtocol needs to be an @objc protocol :-( So this won't work because [String: () -> Void] "cannot be represented in ObjectiveC". Any other idea? – otusweb Nov 29 '18 at 14:13
  • You'd have to make it `[String: String]` and convert Selectors back and forth to Strings (NSStringFromSelector, NSSelectorFromString). I do not generally recommend this kind of automatic discovery, however. I recommend having an explicit step to register your classes. It's generally one line of code up in the app delegate, and it eliminates all kinds of magic and subtle bugs. – Rob Napier Nov 29 '18 at 14:46
  • Thanks, i'll think about using a registration approach instead. Thanks for your help – otusweb Nov 30 '18 at 08:19