Search code examples
iosswiftclosuresalamofire

Swift completion handler for Alamofire seemingly not executing


I have the following function in a class in my program:

    func getXMLForTrips(atStop: String, forRoute: Int, completionHandler: @escaping (String) -> Void) {
        let params = [api key, forRoute, atStop]

        Alamofire.request(apiURL, parameters: params).responseString { response in
            if let xmlData = response.result.value {
                completionHandler(xmlData)
            } else {
                completionHandler("Error")
            }
        }
    }

In the init() for the class, I attempt to call it like this:

getXMLForTrips(atStop: stop, forRoute: route) { xmlData in
                    self.XMLString = xmlData
                }

This compiles without errors, but after init() is executed, my class's self.XMLString is still nil (shown both by the Xcode debugger and by my program crashing due to the nil value later on). I see no reason why this shouldn't work. Can anyone see what I am missing?


Solution

  • You shouldn't be making internet calls in the initializer of a class. You will reach the return of the init method before you go into the completion of your internet call, which means it is possible that the class will be initialized with a nil value for the variable you are trying to set.

    Preferably, you would have another class such as an API Client or Data Source or View Controller with those methods in it. I am not sure what your class with the init() method is called, but lets say it is called Trips.

    class Trips: NSObject {
    
      var xmlString: String
    
      init(withString xml: String) {
        xmlString = xml
      }
    
    }
    

    Then one option is to put the other code in whatever class you are referencing this object in.

    I'm gonna use a view controller as an example because I don't really know what you are working with since you only showed two methods.

    class ViewController: UIViewController {
    
      override func viewDidLoad() {
        super.viewDidLoad() 
        //setting some fake variables as an example
        let stop = "Stop"
        let route = 3
    
        //just going to put that method call here for now
        getXMLForTrips(atStop: stop, forRoute: route) { xmlData in
          //initialize Trip object with our response string
          let trip = Trip(withString: xmlData)
        }
      }
    
      func getXMLForTrips(atStop: String, forRoute: Int, completionHandler: @escaping (String) -> Void) {
        let params = [api key, forRoute, atStop]
        Alamofire.request(apiURL, parameters: params).responseString { response in
          if let xmlData = response.result.value {
            completionHandler(xmlData)
          } else {
            completionHandler("Error")
          }
        }
      }
    
    }
    

    If you want to be able to initialize the class without requiring setting the xmlString variable, you can still do the same thing.

    Change the Trips class init() method to whatever you need it to be and set var xmlString = "" or make it optional: var xmlString: String?.

    Initialize the class wherever you need it initialized, then in the completion of getXMLForTrips, do trip.xmlString = xmlData.