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)
}
}