Swift 5、RxSwift:网络请求与RxSwift

Swift 5, RxSwift: Network request with RxSwift

我开始使用 RxSwift 进行服务调用。 这是我的旧代码:

class Service: GraphQLService {

func graphQL(body: [String: Any?], onSuccess: @escaping (Foundation.Data) throws -> (), onFailure: @escaping (Error) -> ()) {
    
    guard let urlValue = Bundle.main.urlValue else { return }
    guard let url = URL(string: urlValue) else { return
        print("Error with info.plist")
    }
    var request = URLRequest(url: url)
    
    let userKey = Bundle.main.userKeyValue
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue(userKey, forHTTPHeaderField: "userid")

    request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
    
    URLSession.shared.dataTask(with: request) { (data, response, error) in
        if let error = error {
            onFailure(error)
        }

        if let data = data {
            do{
                try onSuccess(data)
            }
            catch{
                onFailure(error)
            }
        }
    }.resume()
}

这里我做的是获取定期存款的功能:

final class TimeDepositManager: Service, TimeDepositManagerProtocol {
let timeDepositQuery = Bundle.main.queryValue

func getTimeDeposits(onSuccess: @escaping ([TimeDeposits]) -> (), onFailure: @escaping (Error) -> ()) {
    let body = ["query": timeDepositQuery]
    Service().graphQL(body: body, onSuccess: { data in
        let json = try? JSONDecoder().decode(GraphQLResponse.self, from: data)
        onSuccess(json?.data?.account?.timeDeposits ?? [])
    }, onFailure: onFailure)
}

到目前为止,这是我使用 RxSwift 的代码:

class Service: GraphQLService {

func graphQL(body: [String : Any?]) -> Observable<Foundation.Data> {
    
    return Observable.create { observer in
        
        let urlValue = Bundle.main.urlValue
        let url = URL(string: urlValue ?? "")
        
        let session = URLSession.shared
        var request = URLRequest(url: url!)
        let userKey = Bundle.main.userKeyValue
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(userKey, forHTTPHeaderField: "userid")
        request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
        
        session.dataTask(with: request) { (data, response, error) in
            if let error = error {
                observer.onError(error)
            }
            
            if let data = data {
                do{
                    try onSuccess(data)
                    observer.onNext(data)
                }
                catch{
                    //onFailure(error)
                    observer.onError(error)
                    print("Error: \(error.localizedDescription)")
                }
            }
        }.resume()
        return Disposables.create {
            session.finishTasksAndInvalidate()
        }
    }
}

这是我不明白如何在 getTimeDeposits () 中使用 try 进行反序列化的地方? JSONDecoder () ... 使用 RxSwift 而不使用 onSuccess?

final class TimeDepositManager: Service, TimeDepositManagerProtocol {
let timeDepositQuery = Bundle.main.queryValue

func getTimeDeposits() -> Observable<[TimeDeposits]> {
    let body = ["query": timeDepositQuery]
    Service().graphQL(body: body)
}

您也可以拥有 getTimeDeposits() return 一个 Observable,并在 map 闭包中处理反序列化。还有一些事情。

  • RxCocoa 已经在 URLSession 上有一个方法,所以你不需要自己写。
  • 我建议减少发出网络请求的函数中的代码量。您希望能够在不实际发出请求的情况下测试发出请求的逻辑。

像这样:

final class TimeDepositManager: Service, TimeDepositManagerProtocol {
    let timeDepositQuery = Bundle.main.queryValue

    func getTimeDeposits() -> Observable<[TimeDeposits]> {
        let body = ["query": timeDepositQuery]
        return Service().graphQL(body: body)
            .map { try JSONDecoder().decode(GraphQLResponse.self, from: [=10=]).data?.account?.timeDeposits ?? [] }
    }
}

class Service: GraphQLService {

    func graphQL(body: [String: Any?]) -> Observable<Data> {
        guard let urlValue = Bundle.main.urlValue else { fatalError("Error with info.plist") }
        let request = urlRequest(urlValue: urlValue, body: body)
        return URLSession.shared.rx.data(request: request) // this is in RxCocoa
    }

    func urlRequest(urlValue: String, body: [String: Any?]) -> URLRequest {
        guard let url = URL(string: urlValue) else { fatalError("Error with urlValue") }
        var request = URLRequest(url: url)
        let userKey = Bundle.main.userKeyValue
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue(userKey, forHTTPHeaderField: "userid")
        request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
        return request
    }
}

如果你出于某种原因不想使用 RxCocoa,这里是包装 URLSession.dataTask 方法的正确方法:

extension URLSession {
    func data(request: URLRequest) -> Observable<Data> {
        Observable.create { observer in
            let task = self.dataTask(with: request, completionHandler: { data, response, error in
                guard let response = response as? HTTPURLResponse else {
                    observer.onError(URLError.notHTTPResponse(data: data, response: response))
                    return
                }
                guard 200 <= response.statusCode && response.statusCode < 300 else {
                    observer.onError(URLError.failedResponse(data: data, response: response))
                    return
                }
                guard let data = data else {
                    observer.onError(error ?? RxError.unknown)
                    return
                }
                observer.onNext(data)
                observer.onCompleted() // be sure to call `onCompleted()` when you are done emitting values.
                // make sure every possible path through the code calls some method on `observer`.
            })
            return Disposables.create { task.cancel() } // don't forget to handle cancelation properly. You don't want to kill *all* tasks, just this one.
        }
    }
}

enum URLError: Error {
    case notHTTPResponse(data: Data?, response: URLResponse?)
    case failedResponse(data: Data?, response: HTTPURLResponse)
}