Search code examples
iosswiftrx-swift

loading behavior do not stop when error occur


I make a generic function using Alamofire

This is function

class APIServices {
    
    private init() {}
    static let instance = APIServices()
    
    func getData<T: Decodable, E: Decodable>(url: String, method: HTTPMethod ,params: Parameters?, encoding: ParameterEncoding ,headers: HTTPHeaders? ,completion: @escaping (T?, E?, Error?)->()) {
        
        AF.request(url, method: method, parameters: params, encoding: encoding, headers: headers)
            .validate(statusCode: 200...300)
            .responseJSON { (response) in
                switch response.result {
                case .success(_):
                    guard let data = response.data else { return }
                    do {
                        let jsonData = try JSONDecoder().decode(T.self, from: data)
                        completion(jsonData, nil, nil)
                    } catch let jsonError {
                        print(jsonError)
                    }
                    
                case .failure(let error):
                    // switch on Error Status Code
                    guard let data = response.data else { return }
                    guard let statusCode = response.response?.statusCode else { return }
                    switch statusCode {
                    case 400..<500:
                        do {
                            let jsonError = try JSONDecoder().decode(E.self, from: data)
                            completion(nil, jsonError, nil)
                        } catch let jsonError {
                            print(jsonError)
                        }
                    default:
                        completion(nil, nil, error)
                    }
                }
            }
    }
}

and use it in HomeViewModel

class HomeViewModel {
    
    var loadingBehavior  = BehaviorRelay<Bool>(value: false)
    
    private var homeModelSubject = PublishSubject<[Book]>()
    private var isTableHidden    = BehaviorRelay<Bool>(value: false)
    
    var homeModelObservable: Observable<[Book]> {
        return homeModelSubject
    }
    
    var isTableHiddenObservable:Observable<Bool> {
        return isTableHidden.asObservable()
    }
    
    
    func getBooks(handler: @escaping networkHandler) {
        loadingBehavior.accept(true)
        let url = "https://simple-books-api.glitch.me/books"
        APIServices.instance.getData(url: url, method: .get, params: nil, encoding: JSONEncoding.default, headers: nil) {[weak self] (bookModel: [Book]?, baseError: HomeBaseError?, error) in
            guard let self = self else { return }
            self.loadingBehavior.accept(false)
            if let error = error {
                print("this is error discription\(error.localizedDescription)")
            } else if let baseError = baseError {
                print(baseError.error)
            } else {
                guard let bookModel = bookModel else { return }
                if bookModel.count  > 0 {
                    self.homeModelSubject.onNext(bookModel)
                    self.isTableHidden.accept(false)
                } else {
                    self.isTableHidden.accept(true)
                }
            }
        }
        
    }
}

the problem is when close wifi loading do not stop and do not print error.localizedDescription and when I make debug do not go to if let error ...


Solution

  • It's hard to understand exactly what goes wrong but I would suggest removing the obvious bugs you have in (at least?) 4 places where you do not use your completion handler.

    The first one is

    guard let data = response.data else { return }
    

    This should be changed to

    guard let data = response.data else { 
        completion(nil, nil, nil)
        return
    }
    

    or if you consider this to be an error you might want something like

    guard let data = response.data else { 
        completion(nil, nil, NoDataError())
        return
    }
    

    where NoDataError is a custom Error you need to create. You need to fix this in two places.

    The other situation is when you get a decoding error (also in two places)

    } catch let jsonError {
         print(jsonError)
    }
    

    Here you should also use your completion handler

    } catch let jsonError {
         print(jsonError)
         completion(nil, nil, jsonError)
    }
    

    You also have a logical bug when receiving an error (failure) because you are checking the http status code before decoding but you will never get a response if you have a status code > 200

    So when you get a .failure you should only decode the error, the whole response status logic you have needs to be separated from your success/failure logic