Search code examples
classgenericsswiftclass-method

Call Class Methods From Protocol As Parameter


I want to be able to pass a class (not an initialized object) of a certain protocol type to a method, then call the class functions of that class in the method. Code below.

I am using Swift and have an protocol defined like this

    //Protocol for any object to be used with an FAUAPIConnection
protocol FAUAPIModel{

    //Used to parse the object from a given dictionary to an object
    class func parseFromJSON(JSON:AnyObject) -> Self

    //Required default init
    init()

}

What I would like to do is have a method like this

    func getSomeParsingDone<T:FAUAPIModel>(model:T.Type? = nil, getPath:path, callingObj:CallingClass) -> Void
    {
        //GetIt is inconsequential, just logic to get an object from a certain path
         var returnObj:AnyObject = GetIt.get(path)
         if(model != nil){
            returnObj = model!.parseFromJSON()  <<<<<< Type 'T' does not conform to protocol 'AnyObject'
         }
         callingObj.done(returnObj)
    }

Object that implements the protocol

import Foundation

class MyObj: FAUAPIModel{  

   var neededVal:String
   var nonneededVal:String

  required convenience init(){
    self.init(neededVal:"VALUE")
  }

  init(neededVal:String, nonneededVal:String = ""){
    self.neededVal = neededVal
    self.nonneededVal = nonneededVal
  }

  class func parseFromJSON(JSON:AnyObject) -> WGMPart
  {
     return WGMPart() <<<<<<<< Method 'parseFromJSON' in non-final class 'WGMPart' must return 'Self' to conform to protocol 'FAUAPIModel'
  }

}

However, I keep getting two errors. I have indicated these above with '<<<<<<<<<<<<'

compile error.


Solution

  • Lots of little things to consider here, but let's get to the heart of your question. The signature you want looks like this:

    func getSomeParsingDone<T:FAUAPIModel>(model:T.Type, path:String) -> T?
    

    I'm making the return optional beause there are a lot of things that could fail here, and you really shouldn't turn all of those into crashes.

    I'd recommend your protocol look like this:

    protocol FAUAPIModel {
        class func parseFromJSON(JSON:AnyObject) -> Self
    }
    

    That way, you're promising that your return your own class, not just anything that is parseable. That does tend to mean that you need to make your classes final. If you don't want them to be final, you'll need to promise some init method in order to construct it. See Protocol func returning Self for more details on how to deal with that if you need it.

    So putting it together, it might look something like this in practice:

    protocol FAUAPIModel {
      class func parseFromJSON(JSON:AnyObject) -> Self
    }
    
    func createObjectOfClass<T: FAUAPIModel>(model: T.Type, path: String) -> T? {
      if let json: AnyObject = GetJSON(path) {
        return model.parseFromJSON(json)
      }
      return nil
    }
    
    // Bogus JSON reader
    func GetJSON(path: String) -> AnyObject? {
      let json: AnyObject? = NSJSONSerialization.JSONObjectWithData(path.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!, options: NSJSONReadingOptions(0), error: nil)
      return json
    }
    
    // Bogus model class that returns trivial version of itself
    final class Something: FAUAPIModel {
      class func parseFromJSON(JSON:AnyObject) -> Something {
        return Something()
      }
    }
    
    // Using it
    let something = createObjectOfClass(Something.self, "/path/to/file")