Downcast Generic AnyObject для протокола Связанный тип Self.Model

Я разрабатываю библиотеку Restofire, в которой я хочу сохранить объект конфигурации. Я хочу иметь ResponseSerializer в объекте конфигурации, но дело в том, что ResponseSerializer является общим.

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

}

Я установил defaultConfiguration с помощью baseUrl Configuration.defaultConfiguration.baseUrl = "http://httpbin.org/"

У меня есть протокол с требованием связанного типа, который использует стандартную конфигурацию по умолчанию как реализацию по умолчанию. Но мне нужно изменить общий AnyObject на связанную модель типа, чтобы responseSerializer объекта конфигурации возвращал тип 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
    }

}

Я получаю сообщение об ошибке Cannot convert return expression of Configuration<AnyObject> to return type Configuration <Self.Model>

Как я могу отказаться от использования модели вместо AnyObject?

У меня также есть протокол Requestable, который наследуется от 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 в https://github.com/Restofire/Restofire/compare/Add/DefaultResponseSerializer

Я мог бы сделать непосредственно ниже, но тогда пользователь не сможет установить его с помощью объекта конфигурации.

// MARK: - Default Implementation
public extension Requestable {

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

}

Есть ли другой способ?

Ответ 1

То, что вы пытаетесь сделать, невозможно. Это происходит из-за сильной системы безопасности в Swift.

Похоже, вы хотите ограничить configuration связанным типом Model, но вы также хотите предоставить конфигурацию по умолчанию. Проблема здесь в том, что ваша конфигурация по умолчанию не гарантирует, что ее общий тип будет таким же, как тип Model.

Система типа Swift не позволит вам пройти <AnyObject> как тип <Model>, так как он не может гарантировать, что объект действительно является IS типа Model.

Когда вы делаете что-то соответствовать Configurable и объявляете тип Model, вы говорите: "Моя конфигурация должна использовать тип, который я дал". defaultConfiguration не может гарантировать этого, поэтому его нельзя использовать таким образом.

От взгляда на ваш код вы используете это, чтобы определить тип используемого responseSerializer. Но, если вы используете разные объекты Configurable, все они будут нуждаться в разных responseSerializer s, поэтому они не могут использовать конфигурацию по умолчанию.

Я потратил много времени на размышления об этой проблеме, и я не вижу никакого отношения к ней. Вам придется каким-то образом изменить свой дизайн.

Если вы переместите responseSerializer на протокол Configurable, вы можете сделать configuration не общий. В этом случае вы могли бы создать responseSerializer в расширении протокола на Configurable. Если вам нужно использовать responseSerializer с конфигурацией независимо от объекта Configurable, тогда вам нужно будет создать responseSerializer<AnyObject, NSError>, где бы вы ни использовали. Я не знаком со всем дизайном и намерением вашей библиотеки, поэтому я не уверен, что это будет работать для того, чего вы пытаетесь достичь.

Единственное, что я могу сказать вам с уверенностью, это то, что ваш дизайн должен измениться, чтобы использовать defaultConfiguration.