Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Downcast Generic AnyObject to Protocol Associated Type Self.Model

I am developing a library Restofire in which i want to keep a configuration object. I want to have a ResponseSerializer in the configuration object but the thing is ResponseSerializer is a generic.

public struct Configuration<M> {

    /// The Default `Configuration`. 
    static let defaultConfiguration = Configuration<AnyObject>()

    /// The base URL. `nil` by default.
    public var baseURL: String!

    /// The `ResponseSerializer`
    public var responseSerializer: ResponseSerializer<M, NSError> = AlamofireUtils.JSONResponseSerializer()

    /// The logging, if enabled prints the debug textual representation of the 
    /// request when the response is recieved. `false` by default.
    public var logging: Bool = false

}

I set up the defaultConfiguration with baseUrl Configuration.defaultConfiguration.baseUrl = "http://httpbin.org/"

I have a protocol with associatedType requirement which uses the defaultConfiguration as default implementation. But i require to change the generic AnyObject to the associatedType Model so the responseSerializer of configuration object returns the type Model.

public protocol Configurable {

    associatedtype Model

    /// The Restofire configuration.
    var configuration: Configuration<Model> { get }

}

public extension Configurable {

    /// `Restofire.defaultConfiguration`
    // Cannot convert return expression of Configuration<AnyObject> to return type Configuration <Self.Model>
    public var configuration: Configuration<Model> {
        return Restofire.defaultConfiguration
    }

}

I get the error Cannot convert return expression of Configuration<AnyObject> to return type Configuration <Self.Model>

How can i downcast to use Model instead of AnyObject ?

I also have a protocol Requestable that inherits from Configurable

public protocol Requestable: Configurable {

    /// The type of object returned in response.
    associatedtype Model

    /// The base URL.
    var baseURL: String { get }

    /// The path relative to base URL.
    var path: String { get }

    /// The request parameters.
    var parameters: AnyObject? { get }

    /// The logging.
    var logging: Bool { get }

    /// The Response Serializer
    var responseSerializer: ResponseSerializer<Model, NSError> { get  }
}

// MARK: - Default Implementation
public extension Requestable {

    /// `configuration.BaseURL`
    public var baseURL: String {
        return configuration.baseURL
    }

    /// `nil`
    public var parameters: AnyObject? {
        return nil
    }

    /// `configuration.logging`
    public var logging: Bool {
        return configuration.logging
    }

    /// `configuration.responseSerializer`
    var responseSerializer: ResponseSerializer<Model, NSError> {
         return configuration.responseSerializer
    } 

}

RealCode at https://github.com/Restofire/Restofire/compare/Add/DefaultResponseSerializer

I could do directly below but then the user will not be able to set it using the configuration object.

// MARK: - Default Implementation
public extension Requestable {

    /// `configuration.responseSerializer`
    var responseSerializer: ResponseSerializer<Model, NSError> {
         return AlamofireUtils.JSONResponseSerializer()
    } 

}

Is there any other way ?

like image 240
Rahul Katariya Avatar asked Apr 25 '16 08:04

Rahul Katariya


1 Answers

What you are trying to do is not possible. This is because of the strong type safety system in Swift.

It looks like you want to restrict the configuration to the associated type Model, but you also want to provide a default configuration. The issue here is that your default configuration has no guarantee that its generic type will be the same as the type of Model.

The Swift type system won't let you pass <AnyObject> as type <Model>, as it has no way to ensure that the object actually IS of the Model type.

When you make something conform to Configurable and declare the type of the Model, you are saying, "My configuration must use the type I've given." The defaultConfiguration can't guarantee that, so it cannot be used in this way.

From looking at your code, you are using this to determine the type of the responseSerializer to use. But, if you are using different Configurable objects, they will all need different responseSerializers, so they can't all use the default configuration.

I've spent a lot of time thinking about this problem, and I don't see any way around it. You are going to have to change your design in some way.

If you move the responseSerializer on to the Configurable protocol, then you could make the Configuration non-generic. In that case, you would be able to create the responseSerializer in a protocol extension on Configurable. If you need to use the responseSerializer with a configuration independently of a Configurable object, then you would have to create a responseSerializer<AnyObject, NSError> wherever it is that you are using it. I'm not familiar with the entire design and intent of your library, so I'm not sure if this will work for what you are trying to achieve.

The one thing I can tell you with certainty is that your design has to change in order to use the defaultConfiguration.

like image 129
AnthonyMDev Avatar answered Nov 17 '22 14:11

AnthonyMDev