使用模型的通用值(相对于静态 model.self)使用 JSONDecoder 进行解析

Using a generic value for the model (vs a static model.self) to be parsed with JSONDecoder

场景:我想使用一个通用函数来访问各种端点。所以我正在尝试创建样板代码来处理各种数据模型 (struct.self) 及其关联的 URLs.

这里有几个模型共享一个共同点 URL会话 + 解码代码:

// MARK: - Region
struct Region: Codable {
    let country: String
    let subregions: [String]
}


// MARK: - SubRegion
struct SubRegion: Codable {
    let country: String
    let subregion: Sub
    let data: [Datum]
}

这是一个数据向量 (Region),其中包含 URL 及其关联的数据模型:

struct URLDataModel {
    var url = URL(string: "https://disease.sh/v3/covid-19/apple/countries/Canada")
    var dataModel: Any = Region.self
}

以下是包含使用 URLDataModel 结构的共享 getRegionList() 的整个 class:

class CountryRegionListModelView: ObservableObject {
    @Published var countryRegionList = [String]()

    // Data Persistence:
    var cancellables: Set<AnyCancellable> = []

    // TODO: --- Get Region tag ---
    func getRegionList(urlDataModel: URLDataModel) {
        // MARK: - Region

        struct Country: Codable {
            let countries: [String]
        }
        struct Region: Codable {
            let country: String
            let subregions: [String]
        }
        print("url: ", urlDataModel.url)

        let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: urlDataModel.url!)
            // the dataTaskPublisher output combination is (data: Data, response: URLResponse)
            .map(\.data)
            .receive(on: DispatchQueue.main)
            .print("Hello Data")
            .decode(type: urlDataModel.dataModel, decoder: JSONDecoder())

        remoteDataPublisher
            .sink(receiveCompletion: { completion in
                switch completion {
                case .finished:
                    break
                case let .failure(anError):
                    Swift.print("received error: ", anError)
                }
            }, receiveValue: { someValue in
                self.countryRegionList = someValue.subregions
                print(self.countryRegionList)

            }).store(in: &cancellables)
    }
}

问题:我不知道如何让JSON解码器与变量模型类型一起工作'model'.self.

我应该使用泛型吗?
那将如何工作?

您可以创建协议:

protocol URLResource {
    associatedtype DataModel: Decodable
    var url: URL? { get }
}

及其示例实现:

struct CovidResource: URLResource {
    typealias DataModel = Region
    var url = URL(string: "https://disease.sh/v3/covid-19/apple/countries/Canada")
}

然后您可以使 getRegionList 通用并接受 some URLResource:

func getRegionList<Resource>(urlDataModel: Resource) where Resource: URLResource {
    let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: resource.url!)
        // the dataTaskPublisher output combination is (data: Data, response: URLResponse)
        .map(\.data)
        .receive(on: DispatchQueue.main)
        .decode(type: Resource.DataModel.self, decoder: JSONDecoder())

    // ...
}

我终于成功了;通过故事板:

import Combine
import SwiftUI

protocol URLResource {
    associatedtype DataModel: Decodable
    var url: URL? { get }
}

var cancellables: Set<AnyCancellable> = []

struct CovidResource<T:Decodable>: URLResource {
    typealias DataModel = T
    var url = URL(string: "https://disease.sh/v3/covid-19/apple/countries/Canada")
}

// MARK: - Canada
struct Canada: Codable {
    let country: String
    let subregions: [String]
}

let myURL = URL(string: "https://disease.sh/v3/covid-19/apple/countries/Canada")!

func getRegionList<Resource>(urlDataModel: Resource) where Resource: URLResource {
    let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: urlDataModel.url!)
        .map(\.data)
        .receive(on: DispatchQueue.main)
        .decode(type: Resource.DataModel.self, decoder: JSONDecoder())
        .print("Remote Publisher \(urlDataModel.url!): ")
    
    remoteDataPublisher
        .sink(receiveCompletion: { completion in
            print(completion)
            switch completion {
            case .finished:
                print("Publisher Finished")
                break
            case let .failure(anError):
                Swift.print("received error: ", anError)
            }
        }, receiveValue: { someValue in
            
            print("someValue: ", someValue)

        }).store(in: &cancellables)
}

// ---------------------------------------

let resource = CovidResource<Canada>(url: myURL)

getRegionList(urlDataModel: resource)

print("End of Proof-of-Concept")

print("The End.")

控制台输出:

Remote Publisher https://disease.sh/v3/covid-19/apple/countries/Canada: : receive subscription: (Decode)
Remote Publisher https://disease.sh/v3/covid-19/apple/countries/Canada: : request unlimited
End of Proof-of-Concept
The End.
Remote Publisher https://disease.sh/v3/covid-19/apple/countries/Canada: : receive value: (Canada(country: "Canada", subregions: ["Alberta", "Calgary", "Edmonton", "British Columbia", "Vancouver", "Manitoba", "New Brunswick", "Newfoundland and Labrador", "Northwest Territories", "Halifax", "Nova Scotia", "Ontario", "Ottawa", "Toronto", "Prince Edward Island", "Montreal", "Quebec", "Saskatchewan", "All", "Yukon Territory"]))
someValue:  Canada(country: "Canada", subregions: ["Alberta", "Calgary", "Edmonton", "British Columbia", "Vancouver", "Manitoba", "New Brunswick", "Newfoundland and Labrador", "Northwest Territories", "Halifax", "Nova Scotia", "Ontario", "Ottawa", "Toronto", "Prince Edward Island", "Montreal", "Quebec", "Saskatchewan", "All", "Yukon Territory"])
Remote Publisher https://disease.sh/v3/covid-19/apple/countries/Canada: : receive finished
finished
Publisher Finished