ios – How to specify the element type in an array in an associated type protocol


I’m not entirely sure that I understand the ask, but this compiles and might be what you want:

protocol BaseRequest {
    associatedtype DataType
    var call0: Single<Data> { get }
}

struct ApiResult: Decodable { }

struct Api: BaseRequest {
    typealias DataType = [ApiResult]
    var call0: RxSwift.Single<Data>
}

extension BaseRequest where DataType == [ApiResult] {
    var call: Single<DataType?> {
        self.call0.map { DataType.deserialize(from: $0) }
    }
}

extension Array where Element == ApiResult {
    static func deserialize(from data: Data) -> [ApiResult] {
        (try? JSONDecoder().decode([ApiResult].self, from: data)) ?? []
    }
}

let api = Api(call0: .just(Data()))

api.call.subscribe(onSuccess: { result in
    // `result` is an `[ApiResult]` as you wanted.
})

That said, this feels like a very strange setup. I prefer something more like this:

// this part is the custom code for your endpoint
struct ApiResult: Decodable { }

extension Endpoint where Response == [ApiResult] {
    static var apiResults: Endpoint {
        // make your request here
        let request = URLRequest(url: URL(string: "whatever")!)
        return Endpoint(request: request, decoder: JSONDecoder())
    }
}

let api = Api(data: URLSession.shared.rx.data(request:)) // pass in a mock for testing.

api.call(endpoint: .apiResults)
    .subscribe(onSuccess: { apiResults in
        // here you go.
    })

// this part is the reusable library. It will work for every conceivable
//   REST request, no matter how complex.
struct Endpoint<Response> {
    let request: URLRequest
    let response: (Data) throws -> Response

    init(request: URLRequest, response: @escaping (Data) throws -> Response) {
        self.request = request
        self.response = response
    }
}

extension Endpoint where Response: Decodable {
    init(request: URLRequest, decoder: TopLevelDecoder) {
        self.request = request
        self.response = { try decoder.decode(Response.self, from: $0) }
    }
}

extension Endpoint where Response == Void {
    init(request: URLRequest) {
        self.request = request
        self.response = { _ in }
    }
}

class Api {
    let data: (URLRequest) -> Observable<Data>
    
    init(data: @escaping (URLRequest) -> Observable<Data>) {
        self.data = data
    }

    func call<Response>(endpoint: Endpoint<Response>) -> Single<Response> {
        data(endpoint.request)
            .map(endpoint.response)
            .asSingle()
    }
}

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img