Trying to call selector to static function in swif

2019-07-28 21:13发布

问题:

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?

回答1:

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()