Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait for a function to end on iOS/Swift, before starting the second one

Tags:

ios

swift

I basically have to methods, which are being called in my viewDidLoad. The first one gets the user's search preferences and saves the preferences to variables at the top. After that I want to access and work with these variables in the second function. But now the variables are always nil when I want to access them in the second function.

How do I need to adjust my viewDidLoad, so the second function is only executed once and my data request has been performed successfully?

var searchLocation = String()
var searchLocationCoordinates = [String:Double]()
var searchRange = Int()


override func viewDidLoad() {
    super.viewDidLoad()

    // Gets the user's search preference
    getTheSearchLocationAndRange()

    // Loads the data from the database
    loadDataFromDatabase()
}

I have read stuff so far with dispatch_asynch or completion handler. Maybe someone can post some code which I can use in my viewDidLoad and makes it work?

like image 512
makle Avatar asked Apr 24 '16 22:04

makle


People also ask

How can I make a function execute every second in Swift?

You can use one like this: var timer = NSTimer() override func viewDidLoad() { scheduledTimerWithTimeInterval() } func scheduledTimerWithTimeInterval(){ // Scheduling timer to Call the function "updateCounting" with the interval of 1 seconds timer = NSTimer.

Does Return exit the function Swift?

Show activity on this post. Answer extracted from question: It seems the problem is, that swift does not exit the function instantly on a return statement in a void function and uses the consecutive void value as the function parameter and terminates as expected if it is non-void.


3 Answers

You can use Swift closures! They are made for that.

Please refer to the Apple guide: Closures

Here's the code you need in your particular case.

FinishedDownload is the closure. When getTheSearchLocationAndRange() is called, its code is executed until the completed() line which waits for all processes of the function to finish. Once the processes finish (downloads for example), completed() calls the closure which activates the code defined in getTheSearchLocationAndRange { () -> () in. Therefore, loadDataFromDatabase() is only called once getTheSearchLocationAndRange() has entirely finished executing and the data is present (not nil).

    var searchLocation = String()
    var searchLocationCoordinates = [String:Double]()
    var searchRange = Int()
    typealias FinishedDownload = () -> ()

    override func viewDidLoad() {
        super.viewDidLoad()

        getTheSearchLocationAndRange()
    }

    func getTheSearchLocationAndRange(completed: FinishedDownload) {

           // Code for searching Location Range HERE

           completed()
    }

    getTheSearchLocationAndRange { () -> () in
        loadDataFromDatabase()
    }

I hope this solved your issue and answered your question :)

BTW, about the "leaving your GUI hanging" part, Alamofire takes automatically care of this for you. If you don't use Alamofire, then you will have to manually assign the asynchronous request to a background thread so your GUI doesn't become unresponsive.

like image 55
Coder1000 Avatar answered Oct 14 '22 12:10

Coder1000


I wrote a demo project and posted it on GitHub that simulates handling an asynchronous network download. Take a look at DuncanMC/SwiftCompletionHandlers.

Specifically look at the method asyncFetchImage(), which does almost exactly what this thread is talking about: Uses an asynchronous method internally, and takes a completion block that it calls once the asynchronous load is done.

That is the general pattern you should use. Write a method that takes a completion block/closure. Internally, have that method call whatever asynchronous function it needs and then call your completion closure from inside the asynchronous method call's completion closure.

The function asyncFetchImage looks like this:

func asyncFetchImage(imageName imageName: String,
  completion: (
    image: UIImage?,
    status: String) -> ())
{
  print("Entering \(#function)")

  //Simulate a network operation by waiting a few seconds before loading an image
  let nSecDispatchTime = dispatch_time(DISPATCH_TIME_NOW, Int64(3.0 * Double(NSEC_PER_SEC)))
  let queue = dispatch_get_main_queue()
  dispatch_after(nSecDispatchTime, queue)
    {
      () -> Void in
      let result = UIImage(named: imageName)
      print("Loading image in background")
      let status = result != nil ? "image loaded" : "Error loading image"
      print("About to call completion handler")
      completion(image: result, status: status)
  }
  print("Leaving \(#function)")
}
like image 33
Duncan C Avatar answered Oct 14 '22 10:10

Duncan C


Okay I've found an solution. I basically called the function at the end of the first one.

So basically:

    var searchLocation = String()
    var searchLocationCoordinates = [String:Double]()
    var searchRange = Int()


    override func viewDidLoad() {
        super.viewDidLoad()

        // Gets the user's search preference
        getTheSearchLocationAndRange()
    }

    func getTheSearchLocationAndRange() {
    // Code for getTheSearchLocationAndRange()

    loadDataFromDatabase()
    }

    func loadDataFromDatabase(){
    // Code for loadDataFromDatabase()
    }
like image 41
makle Avatar answered Oct 14 '22 10:10

makle