Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
197 views
in Technique[技术] by (71.8m points)

ios - Waiting for Asynchronous function call to complete

I know this question has been asked before, but all solutions do not work for me.

I have a function with sends parameters to an API, and returns the data as a list, I have a UITableView set up to use that list, but it runs before the list is assigned to a variable.

code:

var functionResult = [String]()
override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        //gradesscrollView.contentSize.height = 3000
        fetchItems{ (str) in

            var returnedItems = [String]()

            let result = self.convertoArray(itemstoPass: str!)
            for i in result{
                functionResult.append(i)
            }
        }

        self.tableofItems.delegate = self
        self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.


}

I would appreciate it if it is not immediately voted as a duplicate, here is what I have tried.

  1. Dispatch groups
  2. Semaphore timing
  3. running variables
  4. including self.tableofItems.delegate = self & self.tableofItems.dataSource = self in the fetchItems{ (str) in part.

EDIT: Fetch items has been requested,

func fetchItems(completionHandler: @escaping (String?) -> ()) -> () {
        let headers = [
            "Content-Type": "application/x-www-form-urlencoded"
        ]
        //Switch to keychain
        let username = UserDefaults.standard.object(forKey: "username") as! String?
        let password = UserDefaults.standard.object(forKey: "password") as! String?

        let usernametoSend = username!
        let passwordtoSend = password!

        print(usernametoSend)
        print(passwordtoSend)
        let parameters: Parameters = [
            "username": usernametoSend,
            "password": passwordtoSend
        ]

        Alamofire.request("https://www.mywebsite.com/API/getItems", method: .post, parameters: parameters, headers: headers)
            .responseString { response in
                completionHandler(String(response.result.value!))
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can't - and shouldn't - wait until an async call to complete. You need to study async programming until you understand it.

An async function accepts a job to do, and returns immediately, before the job is done.

in Swift you usually write an async function to take a completion handler, which is a block of code that you want to be run one the async task is complete.

I have a project called Async_demo (link) on Github that illustrates this. It implements a DownloadManager class that handles async downloads.

The key part is the function downloadFileAtURL(), which should more properly be named downloadDataAtURL, since it returns in-memory data rather than a file.

I created that function to take a completion handler as a parameter:

/**
 This function demonstrates handling an async task.
 - Parameter url The url to download
 - Parameter completion: A completion handler to execute once the download is finished
 */

  func downloadFileAtURL(_ url: URL, completion: @escaping DataClosure) {
    
    //We create a URLRequest that does not allow caching so you can see the download take place
    let request = URLRequest(url: url,
                             cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
                             timeoutInterval: 30.0)
    let dataTask = URLSession.shared.dataTask(with: request) {
      //------------------------------------------
      //This is the completion handler, which runs LATER,
      //after downloadFileAtURL has returned.
      data, response, error in
      
      //Perform the completion handler on the main thread
      DispatchQueue.main.async() {
        //Call the copmletion handler that was passed to us
        completion(data, error)
      }
      //------------------------------------------
    }
    dataTask.resume()
    
    //When we get here the data task will NOT have completed yet!
  }
}

It uses an NSURLSession to download a block of data from the specified URL. The data request call I use takes a completion handler that gets executed on a background thread. In the completion handler that I pass to the data task, I invoke the completion handler that's passed in to the downloadFileAtURL() function, but on the main thread.

The code you posted is kind of confusing. It isn't clear which part is the async function, what the flow is, or what data is needed to display your table view.

If you rewrite your function that does async work to take a completion block then you could call tableView.reloadData() in your completion block. (Make sure that call is performed on the main thread.)

EDIT:

As others have said, you need to edit your question to show the code for your fetchItems() function.

I'm guessing that that function is the one that does the Async work, and that the block after it is a completion handler that gets performed asynchronously. If so, you should probably refactor your code like this:

var functionResult = [String]()
override func viewDidLoad() {
        super.viewDidLoad()

        //I moved these lines above the call to fetchItems to make it clear
        //that they run before fetchItems' completion closure is executed
        self.tableofItems.delegate = self
        self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.

        print("Step 1")
        fetchItems{ (str) in

            var returnedItems = [String]()
            
            let result = self.convertoArray(itemstoPass: str!)
            for i in result{
                functionResult.append(i)
            }
            print("Step 3")
            DispatchQueue.main.async() {
              tableview.reloadData() //Do this from the main thread, inside the closure of `fetchItems()`
            }
        }

        print("Step 2")
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...