Search code examples
iosswiftrx-swift

How can I get the value from a service that returns an Observable using RxSwift


I am trying to understand RxSwift. I have created a simple playground below that mocks out the behaviour I am attempting. This should run OK.

import UIKit
import RxSwift

struct Universities: Codable {

    let author: Author
    let example: String
    let github: String

    enum CodingKeys: String, CodingKey {
        case author
        case example
        case github
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        author = try values.decode(Author.self, forKey: .author)
        example = try values.decode(String.self, forKey: .example)
        github = try values.decode(String.self, forKey: .github)
    }

}

struct Author: Codable {

    let name: String
    let website: String

    enum CodingKeys: String, CodingKey {
        case name
        case website
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        website = try values.decode(String.self, forKey: .website)
    }

}


class HTTPClient<T: Codable> {

    private let baseURL = URL(string: "http://universities.hipolabs.com/")!

    func call() -> Observable<T> {
        return Observable<T>.create { observer in
            let request = URLRequest(url: self.baseURL)
            let task: URLSessionDataTask = URLSession.shared.dataTask(with: request) { data, response, error in
                do {
                    let model: T = try JSONDecoder().decode(T.self, from: data!)
                    observer.onNext(model)
                } catch let error {
                    observer.onError(error)
                }
                observer.onCompleted()
            }

            task.resume()

            return Disposables.create {
                task.cancel()
            }
        }
    }
}

class SomeService {
    private let client = HTTPClient<Universities>()
    private let disposeBag = DisposeBag()



    func getValues() {
        client.call().subscribe(onNext: { print($0.example) }).disposed(by: disposeBag)
    }
}

let service = SomeService()

service.getValues()

As you can see SomeService calls my http client which makes a request and returns the response. I then print out a value. What I'm struggling to understand is how can I subscribe to getValues in another class, and make use of the response.

This was my current attempt

class SomeService {
    private let client = HTTPClient<Universities>()
    private let disposeBag = DisposeBag()



    func getValues() -> Observable<String> {
        return client.call().subscribe(onNext: { return $0.example }).disposed(by: disposeBag)
    }
}

let service = SomeService()

service.getValues().map { print($0) }

However I get an error

Cannot convert return expression of type '()' to return type 'Observable'

Should I be creating some sort of subject and subscribing to that from my controller and instead calling onNext with the returned value?

If so how would I structure those calls in my controller?


Solution

  • func getValues() -> Observable<String> {
      return client.call()
         .map { $0.example }
         .do(onNext: { print($0) })
    }
    

    No need to introduce an in between subject. Do onNext will hook on the observable and add the printing behavior you desire. map will transform the universities type to the example string. Then you can simply subscribe to the result of getValues() from your ViewController.