Arrays Swift:能否发现数组中元素的类型并用于指定泛型类型参数?

Arrays Swift:能否发现数组中元素的类型并用于指定泛型类型参数?,arrays,swift,generics,protocols,Arrays,Swift,Generics,Protocols,我有一个名为apirest的协议,它有一个名为ResponseType的关联类型和一个解码函数。这个例子并不完整,但我相信这些是问题的唯一相关部分 还有一个名为ArrayResponse的结构,用于表示网络响应何时作为不同对象的项数组返回(取决于特定的apirest的ResponseType,以及totalItems) protocol APIRequest { associatedtype ResponseType: Codable /// Decodes the reque

我有一个名为
apirest
的协议,它有一个名为
ResponseType
的关联类型和一个解码函数。这个例子并不完整,但我相信这些是问题的唯一相关部分

还有一个名为
ArrayResponse
的结构,用于表示网络响应何时作为不同对象的
项数组返回(取决于特定的
apirest
ResponseType
,以及
totalItems

protocol APIRequest {
    associatedtype ResponseType: Codable

    /// Decodes the request result into the ResponseType
    func decode(_: Result<Data, APIError>) throws -> ResponseType
}

struct ArrayResponse<T>: Codable where T: Codable {
    let items: [T]
    let totalItems: Int
}
BrandRequest
用于从服务器获取单个
Brand
,但我也可以获取
Brand
的数组(由上面的
ArrayResponse
表示,因为Brand是遵循相同模式的许多不同类型之一),使用
BrandsRequest
,将其
ResponseType
指定为
品牌
的数组

struct BrandsRequest: APIRequest {
    typealias ResponseType = [Brand]
}
我决定在协议扩展中做一个默认实现,因为它们都遵循相同的解码,而不是在每个依附于
apirest
的结构中提供
decode
函数

取决于
响应类型
是否为数组(例如
[Brand]
,或单个项目,如
品牌
,我使用了不同版本的
解码
功能。这对单个项目很有效,但对于项目数组,我希望查看数组,发现其元素的类型,并使用它检查
结果是否正确。decoded()
被解码为该特定类型的
ArrayResponse

因此,例如,如果我做一个
品牌请求
,我希望这个顶部的
解码
函数将数组解码返回
(尝试result.decoded()作为ArrayResponse)。带有
品牌
的项目是不同的结构(例如
产品
客户
,等等)取决于此函数接收的数组中元素的类型。此示例包含一些非编译代码,作为我尝试获取
elementType
并将其用作泛型参数的代码,但这当然不起作用。我也不能简单地将
Codable
作为泛型参数传递,因为编译器告诉我:
的值协议类型“Codable”(又名“Decodable&Encodable”)不能符合“Decodable”;只有结构/枚举/类类型才能符合协议

所以我的问题是:

  • 是否有方法捕获数组中要在
    ArrayResponse
    中使用的元素类型
  • 是否有更好的方法来解码网络响应(返回类似
    ArrayResponse
    的项目数组)而不是单个项目响应(如
    Brand
  • 然后将我的数组
    decode
    函数更改为使用
    ElementType
    而不是
    Codable

    extension APIRequest where ResponseType == Array<ElementType> {
        func decode(_ result: Result<Data, APIError>) throws -> ResponseType {
            return (try result.decoded() as ArrayResponse<ResponseType>).items
        }
    }
    

    我的问题的关键是,我想在
    [Brand]
    数组中发现
    品牌
    类型,并将其用于
    数组响应
    解码。

    我怀疑这是对协议的滥用。PATs(具有关联类型的协议)所有这些都是关于向现有类型添加更多功能的,现在还不清楚这是否能做到。相反,我相信您有一个泛型问题

    与前面一样,您有一个
    ArrayResponse
    ,因为这在您的API中是一个特殊的东西:

    struct ArrayResponse<Element: Codable>: Codable {
        let items: [Element]
        let totalItems: Int
    }
    
    (然后在
    init
    中指定适当的解码器)


    看看你链接的代码,是的,这是一个使用协议尝试重新创建类继承的很好的例子。APIRequest扩展是关于创建默认实现,而不是应用通用算法,这通常意味着“继承和重写”OOP思维方式。与一堆符合APIRequest的单个结构不同,我认为作为单个APIRequest通用结构(如上所述)会更好

    但是,您仍然可以在不重写所有原始代码的情况下达到目标。例如,您可以创建一个通用的“数组”映射:

    但现在我们要添加用户:

    struct User: Codable {}
    
    extension Request where Response == User {
        init(userName: String) {
            self.init(urlRequest: URLRequest(url: URL(string: "https://example.com/api/v1/users/\(userName)")!),
                      parser: { try JSONDecoder().decode(User.self, from: $0) }
            )
        }
    }
    
    这几乎是一模一样的。一模一样,我剪切粘贴了它。这告诉我,现在是时候拿出可重用代码了(因为我正在摆脱真正的重复,而不仅仅是插入抽象层)

    但是ArrayResponse呢

    extension Request {
        init<Element: Codable>(domain: String) where Response == ArrayResponse<Element> {
            self.init(urlRequest: URLRequest(url: URL(string: "https://example.com/api/v1/\(domain)")!),
                      parser: { try JSONDecoder().decode(Response.self, from: $0) }
            )
        }
    }
    
    然后可以将信息挂起在模型类型上,如:

    extension User: Fetchable {
        static let domain = "users"
    }
    
    extension ArrayResponse: Fetchable where T: Fetchable {
        static var domain: String { T.domain }
    }
    
    extension Request where Response: Fetchable {
        init(id: String) {
            self.init(domain: Response.domain, id: id)
        }
    
        init<Element: Fetchable>() where Response == ArrayResponse<Element> {
            self.init(domain: Response.domain)
        }
    }
    
    扩展用户:可获取{
    静态let domain=“用户”
    }
    扩展ArrayResponse:Fetchable,其中T:Fetchable{
    静态变量域:字符串{T.domain}
    }
    扩展请求,其中响应:Fetchable{
    init(id:String){
    self.init(域:Response.domain,id:id)
    }
    init()其中Response==ArrayResponse{
    self.init(域:Response.domain)
    }
    }
    
    请注意,这两种方法并不是相互排斥的。您可以同时使用这两种方法,因为这样做是组合的。不同的抽象选择不必相互干扰

    如果你这样做了,你就会开始从我的设计开始,这只是另一种方式。那是关于设计通用代码的方法,而不是具体的实现选择

    所有这些都不需要关联类型。您了解关联类型的方式可能是,不同的一致性类型实现协议要求的方式不同。例如,数组对下标要求的实现与Repeated的实现和LazySequence的实现非常不同。如果协议要求在结构上是相同的,那么您可能看到的是泛型结构(或者可能是类),而不是协议。

    什么是
    .decoded()
    ,为什么您希望它返回
    数组响应
    protocol APIRequest {
        associatedtype ResponseType: Codable
        associatedtype ElementType: Codable
    
        /// Decodes the request result into the ResponseType
        func decode(_: Result<Data, APIError>) throws -> ResponseType
    }
    
    extension APIRequest where ResponseType == Array<ElementType> {
        func decode(_ result: Result<Data, APIError>) throws -> ResponseType {
            return (try result.decoded() as ArrayResponse<ResponseType>).items
        }
    }
    
    struct BrandRequest: APIRequest {
        typealias ResponseType = Brand
        typealias ElementType = Brand
    }
    
    struct BrandsRequest: APIRequest {
        typealias ResponseType = [Brand]
        typealias ElementType = Brand
    }
    
    struct ArrayResponse<Element: Codable>: Codable {
        let items: [Element]
        let totalItems: Int
    }
    
    struct Request<Response: Codable> {
        // You need some way to fetch this, so I'm going to assume there's an URLRequest
        // hiding in here.
        let urlRequest: URLRequest
    
        // Decode single values
        func decode(_ result: Result<Data, APIError>) throws -> Response {
            return try JSONDecoder().decode(Response.self, from: result.get())
        }
    
        // Decode Arrays. This would be nice to put in a constrained extension instead of here,
        // but that's not currently possible in Swift
        func decode(_ result: Result<Data, APIError>) throws -> ArrayResponse<Response> {
            return try JSONDecoder().decode(ArrayResponse<Response>.self, from: result.get())
        }
    }
    
    struct Brand: Codable {
        var brandID: Int
        var brandName: String?
    }
    
    // You want "BrandRequest", but that's just a particular URLRequest for Request<Brand>.
    // I'm going to make something up for the API:
    extension Request where Response == Brand {
        init(brandName: String) {
            self.urlRequest = URLRequest(url: URL(string: "https://example.com/api/v1/brands/(\brandName)")!)
        }
    }
    
    struct Request<Response: Codable> {
        // You need some way to fetch this, so I'm going to assume there's an URLRequest
        // hiding in here.
        let urlRequest: URLRequest
        let decoder: (Result<Data, APIError>) throws -> Response
    }
    
    struct ArrayRequest<Element: Codable>: APIRequest {
        typealias ResponseType = [Element]
        typealias ElementType = Element
    }
    
    typealias BrandsRequest = ArrayRequest<Brand>
    
    struct ElementRequest<Element: Codable>: APIRequest {
        typealias ResponseType = Element
        typealias ElementType = Element
    }
    
    typealias BrandRequest = ElementRequest<Brand>
    
    struct Brand: Codable {
        var brandID: Int
        var brandName: String?
    }
    
    struct Request<Response: Codable> {
        let urlRequest: URLRequest
        let parser: (Data) throws -> Response
    }
    
    extension Request where Response == Brand {
        init(brandName: String) {
            self.init(
                urlRequest: URLRequest(url: URL(string: "https://example.com/api/v1/brands/\(brandName)")!),
                parser: { try JSONDecoder().decode(Brand.self, from: $0) }
            )
        }
    }
    
    struct User: Codable {}
    
    extension Request where Response == User {
        init(userName: String) {
            self.init(urlRequest: URLRequest(url: URL(string: "https://example.com/api/v1/users/\(userName)")!),
                      parser: { try JSONDecoder().decode(User.self, from: $0) }
            )
        }
    }
    
    extension Request {
        init(domain: String, id: String) {
            self.init(urlRequest: URLRequest(url: URL(string: "https://example.com/api/v1/\(domain)/\(id)")!),
                      parser: { try JSONDecoder().decode(Response.self, from: $0) }
            )
        }
    }
    
    extension Request where Response == Brand {
        init(brandName: String) {
            self.init(domain: "brands", id: brandName)
        }
    }
    
    extension Request where Response == User {
        init(userName: String) {
            self.init(domain: "users", id: userName)
        }
    }
    
    extension Request {
        init<Element: Codable>(domain: String) where Response == ArrayResponse<Element> {
            self.init(urlRequest: URLRequest(url: URL(string: "https://example.com/api/v1/\(domain)")!),
                      parser: { try JSONDecoder().decode(Response.self, from: $0) }
            )
        }
    }
    
    extension Request {
        static var baseURL: URL { URL(string: "https://example.com/api/v1")! }
    
        init(path: String) {
            self.init(urlRequest: URLRequest(url: Request.baseURL.appendingPathComponent(path)),
                      parser: { try JSONDecoder().decode(Response.self, from: $0) })
        }
    
        init(domain: String, id: String) {
            self.init(path: "\(domain)/\(id)")
        }
    
        init<Element: Codable>(domain: String) where Response == ArrayResponse<Element> {
            self.init(path: domain)
        }
    }
    
    extension Request where Response == Brand {
        init(brandName: String) {
            self.init(domain: "brands", id: brandName)
        }
    }
    
    extension Request where Response == User {
        init(userName: String) {
            self.init(domain: "users", id: userName)
        }
    }
    
    protocol Fetchable: Codable {
        static var domain: String { get }
    }
    
    extension User: Fetchable {
        static let domain = "users"
    }
    
    extension ArrayResponse: Fetchable where T: Fetchable {
        static var domain: String { T.domain }
    }
    
    extension Request where Response: Fetchable {
        init(id: String) {
            self.init(domain: Response.domain, id: id)
        }
    
        init<Element: Fetchable>() where Response == ArrayResponse<Element> {
            self.init(domain: Response.domain)
        }
    }