-3

I am stuck with this situation where I have a custom JSONDecoder struct which contains a private function to decode data, and another function which is exposed, and should return a specific, Decodable type. I would like these functions to throw successively so I only have to write my do/catch block inside the calling component, but I'm stuck with this error on the exposedFunc() function:

Invalid conversion from throwing function of type '(Completion) throws -> ()' (aka '(Result<Data, any Error>) throws -> ()') to non-throwing function type '(Completion) -> ()' (aka '(Result<Data, any Error>) -> ()')

Here is the code:

import Foundation
import UIKit

typealias Completion = Result<Data, Error>

let apiProvider = ApiProvider()

struct DecodableTest: Decodable {
    
}

struct CustomJSONDecoder {
    private static func decodingFunc<T: Decodable>(
        _ response: Completion,
        _ completion: @escaping (T) -> Void
    ) throws {
        switch response {
        case .success(let success):
            try completion(
                JSONDecoder().decode(
                    T.self,
                    from: success
                )
            )
        case .failure(let error):
            throw error
        }
    }
    
    static func exposedFunc(
        value: String,
        _ completion: @escaping (DecodableTest) -> Void
    ) throws {
        apiProvider.request {
            try decodingFunc($0, completion)
        }
    }
}


class CustomViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        do {
            try CustomJSONDecoder.exposedFunc(value: "test_value") { result in
                // Do something with result
            }
        } catch {
            print(error)
        }
    }
}

class ApiProvider: NSObject {
    func request(_ completion: @escaping (Completion) -> ()) {
        
    }
}

Thank you for your help

2 Answers2

2

This defines method that takes a non-throwing function:

class ApiProvider: NSObject {
    func request(_ completion: @escaping (Completion) -> ()) {
        
    }
}

So in all cases, this function must take a Completion and return Void without throwing. However, you pass the following:

    apiProvider.request {
        try decodingFunc($0, completion)
    }

This method does throw (note the uncaught try), so that's not allowed. You need to do something if this fails:

    apiProvider.request {
        do {
            try decodingFunc($0, completion)
        } catch {
            // Here you must deal with the error without throwing.
        }
    }
    }
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thank you for your answer Rob. I understand that, what I wanted to know is if there is something I can do to make `exposedFunc()` a throwing function so the only place where I need the `do/try/catch` block would be in the view controller and this `exposedFunc()` would simply help propagating the initial potential error from `decodingFunc()`. – JamesDerrick248 Oct 19 '22 at 21:54
  • (the reason of my request being because I feel like I’m potentially repeating the same code to catch the error from the ViewController as well as from the `exposedFunc()` method) – JamesDerrick248 Oct 19 '22 at 22:00
  • 1
    It's impossible for `exposedFunc()` to throw in a useful way. `throws` is synchronous, but `apiProvider` is async. It's impossible to determine if the function throws until the API call comes back. All of this is overcomplicated, however. `decodingFunc` takes a completion handler even though it's synchronous. This makes the callers unnecessarily tricky. For an example of how to write what you're trying to write, see https://robnapier.net/a-mockery-of-protocols. But in modern Swift, you'd just write this as `async` as @Jessy notes. The whole point of `async` is this kind of `throws` use case. – Rob Napier Oct 20 '22 at 02:33
  • Thank you very much for the explanation as well as your interesting post. – JamesDerrick248 Oct 20 '22 at 05:42
1

Not using concurrency features is making your code hard to understand. Switch!

final class CustomViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    Task {
      let result = DecodableTest()
      // Do something with result
    }
  }
}

extension DecodableTest {
  init() async throws {
    self = try JSONDecoder().decode(Self.self, from: await APIProvider.data)
  }
}

enum APIProvider {
  static var data: Data {
    get async throws { .init() }
  }
}