Search code examples
swiftxcodenetworkingcombineurlsession

Swift - Combine subscription not being called


Recently, I tried to use freshOS/Networking swift package.

And I read the README file several times and I couldn't make it work with me. I'm trying to get a list of countries using public API services and here's what I did:

Model

import Foundation
import Networking

struct CountryModel: Codable {
    let error: Bool
    let msg: String
    let data: [Country]
}

struct Country: Codable {
    let name: String
    let Iso3: String
}

extension Country: NetworkingJSONDecodable {}
extension CountryModel: NetworkingJSONDecodable {}

/* 
Output
{
     "error":false,
     "msg":"countries and ISO codes retrieved",
     "data":[
          {
               "name":"Afghanistan",
               "Iso2":"AF",
               "Iso3":"AFG"
          }
     ]
}
*/

View Controller + print(data) in callAPI() function does not print

    override func viewDidLoad() {
        super.viewDidLoad()
        
        configureUI()
        callAPI()
        
    }

    fileprivate func configureUI() {
        title = "Choose Country"
        
        view.addSubview(tableView)
        tableView.delegate = self
        tableView.dataSource = self
        tableView.frame = view.bounds
    }

    fileprivate func callAPI() {
        let countriesService = CountriesApi()
        var cancellable = Set<AnyCancellable>()
        
        countriesService.countries().sink(receiveCompletion: { completion in
            switch completion {
            case .finished:
                print("finished") // not printing
                break
            case .failure(let error):
                print(error.localizedDescription)
            }
        }) { data in
            print(data) // not printing
            self.countriesData = data
        }.store(in: &cancellable)
    }

CountriesAPI()

struct CountriesApi: NetworkingService {
    
    let network = NetworkingClient(baseURL: "https://countriesnow.space/api/v0.1")
    
    // Create
    func create(country c: Country) -> AnyPublisher<Country, Error> {
        post("/countries/create", params: ["name" : c.name, "Iso3" : c.Iso3])
    }
    
    // Read
    func fetch(country c: Country) -> AnyPublisher<Country, Error> {
        get("/countries/\(c.Iso3)")
    }
    
    // Update
    func update(country c: Country) -> AnyPublisher<Country, Error> {
        put("/countries/\(c.Iso3)", params: ["name" : c.name, "Iso3" : c.Iso3])
    }
    
    // Delete
    func delete(country c: Country) -> AnyPublisher<Void, Error> {
        delete("/countries/\(c.Iso3)")
    }
    
    func countries() -> AnyPublisher<[CountryModel], Error> {
        get("/countries/iso")
    }
}

I hope someone can help with what I'm missing.


Solution

  • The problem lies in your callAPI() function, if you change your code to this:

        fileprivate func callAPI() {
            let countriesService = CountriesApi()
            var cancellable = Set<AnyCancellable>()
            
            countriesService.countries()
                .print("debugging")
                .sink(receiveCompletion: { completion in
                    switch completion {
                    case .finished:
                        print("finished") // not printing
                        break
                    case .failure(let error):
                        print(error.localizedDescription)
                    }
                }) { data in
                    print(data) // not printing
                    self.countriesData = data
                }.store(in: &cancellable)
        }
    

    Notice I just added the line print("debugging").

    If you run that, you can see in your console that your subscription gets cancelled immediately.

    debugging: receive cancel

    Why? Because your "cancellable" or "subscription" only lives in the scope of your function, thus, it is deallocated immediately.

    What you can do is add the cancellables set as a property in your ViewController, like this:

    final class ViewController: UIViewController {
        
        private var cancellable = Set<AnyCancellable>()
        
        fileprivate func callAPI() {
            // the code you had without the set
            let countriesService = CountriesApi()
            
            countriesService.countries().sink(receiveCompletion: { completion in
                switch completion {
                case .finished:
                    print("finished") // not printing
                    break
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }) { data in
                print(data) // not printing
                self.countriesData = data
            }.store(in: &cancellable)
        }
        
    }