Unable to infer closure type without a type annotation. Swift 5, XCode 15.0, RxSwift

574 Views Asked by At

My first post, so I apologize for lack of verbosity or knowledge. I am very new to Swift. I am using RxSwift to help concurrently run HTTP calls to my backend that I then zip up into a single observable that then is data bound to a view.

For part of the process, I am creating a Single from RxSwift that will then be either zipped or merged with other HTTP responses, depending on the view.

Intially I was getting a "Unable to expression type without a type annotation" with the closure defined inside function call of Single.create() so I broke the closure out. This is what the entire function looks like:

static func SendSingleRequest<ModelType>(_ request: any APIRequest, _ ModelType: some Decodable) -> Single<ModelType> {
        
        let clo : (Single) -> Disposable = { single in
            do{
                self.session.dataTask(with: request.url) { (data, response, error) in
                    
                    do {
                        let model = try JSONDecoder().decode(ModelType.self, from: data ?? Data())
                        single(.success(model))
                    } catch let jsonError as NSError {
                        print("Json decode failed: \(jsonError.localizedDescription)")
                        single(.failure(jsonError))
                    }
                }
                return Disposables.create()
            }
        };
            return Single<ModelType>.create(subscribe: clo)
        }

In essence, I am trying put the closure inside the .create function. I realize the function defintion of .create() include the @escaping keyword, but that doesn't seem to be an issue. Please correct me if I'm wrong.

Finally, an error saying, "Unable to infer closure type without a type annotation." in the closure definition line (line 2) is popping up, when I feel as though I am defining it, and, in all cases, I am returning a Disposable. Again, I am very new to swift.

How do I avoid this error despite explicitly defining the types?

I've have tried different permutaions of function definintions, honestly trial and error. Wasn't expecting much.

I've hasve tried defining the closure in different places, which doesn't change the code in theory, but I was hoping.

I have tried rearranging the overall structure of what is getting passed into this function so I don't have to deal with certain variables, but this is about as simple as I can make it.

Overall, I've been doing a lot of guess and check AFTER reading about the problem, and I either run into another type error or normal error.

1

There are 1 best solutions below

0
Daniel T. On

You aren't defining the limits of your ModelType. It must be Decodable. This will compile:

static func SendSingleRequest<ModelType>(_ request: any APIRequest, _ type: ModelType.Type) -> Single<ModelType>
where ModelType: Decodable {
    Single.create { completion in
        self.session.dataTask(with: request.url) { data, response, error in
            do {
                let model = try JSONDecoder().decode(ModelType.self, from: data ?? Data())
                completion(.success(model))
            } catch {
                completion(.failure(error))
            }
        }
        return Disposables.create()
    }
}

However is it woefully inadequate for what you are trying to do (and in Swift, function names always start with a lower case letter.)

  • The disposable should be canceling the task.
  • You aren't checking the response object at all.
  • Only accepting a URL instead of an entire URLRequest limits your ability to do the standard REST calls.

Your best bet would be to use RxCocoa's data function. It handles all the heavy lifting for you. So a better translation would be something like:

static func sendSingleRequest<ModelType>(_ request: any APIRequest, _ type: ModelType.Type) -> Single<ModelType>
where ModelType: Decodable {
    session.rx.data(request: URLRequest(url: request.url))
        .decode(type: ModelType.self, decoder: JSONDecoder())
        .asSingle()
}

Better still would be to use URLRequest instead of just URL. Also, what about when you want the raw data instead of using JSON? (For example when downloading images.)

Something like this would be far better:

// defines an APIRequest by the URLRequest needed to make the request and the 
// function needed to parse the response
struct APIRequest<Element> {
    let urlRequest: URLRequest
    let handleResponse: (Data) throws -> Element
}

// when you don't need the response, use this init to create the request
extension APIRequest where Element == Void {
    init(urlRequest: URLRequest) {
        self.urlRequest = urlRequest
        self.handleResponse = { _ in }
    }
}

// this init is for when the response is decodable via a JSONDecoder.
// we pass in the Decoder so that we can configure it.
extension APIRequest where Element: Decodable {
    init(urlRequest: URLRequest, decoder: JSONDecoder) {
        self.urlRequest = urlRequest
        self.handleResponse = { try decoder.decode(Element.self, from: $0) }
    }
}

// the actual send is laughably simple when you use the tools available in Rx
extension URLSession {
    func sendSingleRequest<Element>(_ request: APIRequest<Element>) -> Single<Element> {
        self.rx.data(request: request.urlRequest)
            .map(request.handleResponse)
            .asSingle()
    }
}

I use something very much like the above in my CLE library.