1

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.

steventnorris
  • 5,656
  • 23
  • 93
  • 174

2 Answers2

2

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")
Community
  • 1
  • 1
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thank you for this. However, I am still getting errors using the code you provided. I've changed my OP above to show the new code and new errors. Please advise. – steventnorris Sep 26 '14 at 15:05
  • You're overusing `AnyObject` here. Don't try to assign the result of parsing to returnObj. `done()` should take some type, not `AnyObject`. Overuse of `AnyObject` makes your code very complicated. – Rob Napier Sep 26 '14 at 15:11
  • You're returning the wrong type from `parseFromJSON`. It should return a `WGMPart`, not a `FAUAPIModel` – Rob Napier Sep 26 '14 at 15:12
  • General note here is to think much more about your types and what you're really returning. If you're dealing very much with "any object at all; I don't know anything about it except it's an object" (AnyObject), you're probably not thinking through your types enough. Few things should accept AnyObject, fewer things should return it. Almost nothing should store it. – Rob Napier Sep 26 '14 at 15:15
  • I've altered the code above for the parseFromJSON return, but it's still complaining. – steventnorris Sep 26 '14 at 15:19
  • See the section of my answer where I explain `final` and provide links to more information on how to implement. – Rob Napier Sep 26 '14 at 15:20
  • As far as the 'AnyObject' return goes, the complete method could get either a Dictionary of the raw JSON (where AnyObject in this case is either a String, an Array, or another Dictionary per AFHTTPNetworking parsing standards) or the parsed object or nil. I assumed AnyObject would be best for this, then I could check type in the complete method. – steventnorris Sep 26 '14 at 15:21
  • If you really need to return Either, then the correct way to do that is by returning an enum with associated data, not an `AnyObject`. You know something about it; it's one or the other of those options. – Rob Napier Sep 26 '14 at 15:22
  • I read the link you sent, and used a required init because of it. I didn't see any final information in that link. – steventnorris Sep 26 '14 at 15:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/62002/discussion-between-steventnorris-and-rob-napier). – steventnorris Sep 26 '14 at 15:24
1

I just want to note that the answer to your exact question would be to declare your function like this:

func getSomeParsingDone(model:FAUAPIModel.Type? = nil, getPath:path) -> FAUAPIModel
newacct
  • 119,665
  • 29
  • 163
  • 224