Generic completion handler in Swift

I have a method which has a method named performRequest(). It takes a JSONRequest parameter. JSONRequest looks something like this:

public typealias JSONCompletionHandler = ([Entity]?, NSError?) -> Void

public class JSONRequest: Request {
    public var completionHandler: JSONCompletionHandler
    public var endPoint: String
}

And performRequest() looks like this:

  • More than one protocol in a type constraint
  • Swift: How to compare two Dictionary<String, Any> structs?
  • Creating an extension to filter nils from an Array in Swift
  • What does $0 represent in closures in Swift?
  • In Swift, how to generically limit function to types that understand T + T
  • Swift Generics: More specialized than generic?
  • public func performJSONRequest<T where T: Entity>(jsonRequest: JSONRequest, _: Type) {
            // Make a request which returns a data object
            var entities = self.convertJSONData(data, jsonKey: jsonRequest.jsonKey, T.self)
            // Error: 'T' is not identical to 'Entity'
            jsonRequest.completionHandler(entities, error)
    }
    

    As you can see, it calls convertJSONData() which looks like this:

    func convertJSONData<T where T: Entity>(jsonData: AnyObject, _: T.Type) -> [T] {
            // Convert the data into Swift collection classes, enumerate over them, and create model objects
            var json = JSON(data: jsonData as NSData, options: nil, error: nil)
            var entities = [T]()
    
            for obj in json {
                let book = T(json: obj)
                entities.append(book)
            }
    
        return entities
    

    Entity is a protocol which all my model classes, for example Author and Book, conform to.

    It defines one method: init(json: JSON). Since T is defined as T:Entity, I can just call T:(json: obj) to create instances of any class conforming to Entity.

    I want to be able to use performJSONRequest() to perform request for any object conforming to Entity. For example, I want to build a request for Book instances like this:

    var request = JSONRequest(endPoint: "books") { (let object: [Entity]?, let error: NSError?) -> Void in
        // Cast object to [Book] and have fun
    }
    
    performJSONRequest<Book>(request)
    

    I can’t for the life of me find out how I would implement this. Right now, I get an error in the performJSONRequest() method saying 'T' is not identical to 'Entity'. If I define the array in the completion handler as [AnyObject] I get the same error: 'T' is not identical to 'AnyObject'.

    Thanks for any help!

    Solutions Collect From Internet About “Generic completion handler in Swift”

    The solution is to move the generic type up into the JSONRequest class – that way JSONCompletionHandler can be defined with the generic type you’re requesting instead of just the Entity protocol. (Some of your code seemed a little pseudo-, so this might need some tweaking to fit back into your implementation.)

    JSONRequest is now a generic class with an Entity type restraint:

    public class JSONRequest<T: Entity>: Request {
        // completion handler defined in terms of `T`
        public typealias JSONCompletionHandler = ([T]?, NSError?) -> Void
    
        // no further changes        
        public var completionHandler: JSONCompletionHandler
        public var endPoint: String
        public init(endPoint: String, completionHandler: JSONCompletionHandler) {
            self.endPoint = endPoint
            self.completionHandler = completionHandler
        }
    }
    

    performJSONRequest doesn’t need the type passed as a separate parameter any more. Since jsonRequest is specialized, it gets the type information from that parameter:

    public func performJSONRequest<T: Entity>(jsonRequest: JSONRequest<T>) {
        // create array of `T` somehow 
        var entities: [T] = []
        var error: NSError?
    
        // completionHandler expects [T]? and NSError?
        jsonRequest.completionHandler(entities, error)
    }
    

    When creating your JSONRequest instance, the type given in the completion handler (e.g., [Book]?) will set the type for the generic JSONRequest, and hold throughout the process:

    var request = JSONRequest(endPoint: "books") { (books: [Book]?, error) in
        println(books?.count)
    }
    performJSONRequest(request)