0

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!))
Will
  • 4,942
  • 2
  • 22
  • 47

2 Answers2

1

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")
}
Community
  • 1
  • 1
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • Sorry I am very new to Swift – Will Dec 18 '16 at 15:36
  • But, is there anyway? I sort of need to do it for the program,I come from a Python background, where everything is very line by line – Will Dec 18 '16 at 15:40
  • See the edit to my answer. I made an educated guess as to what your code is doing and provided a solution based on that guess. – Duncan C Dec 18 '16 at 15:46
  • Well Duncan C, your code worked! Sorry if this question was dumb. I am very new to app development, and I am very excited to learn. I hope one day I don't have to ask questions on Stack overflow so often. Thank you so much for your help! – Will Dec 18 '16 at 15:51
  • Ok, why the down-vote? If you think my answer leaves something to be desired, post a comment stating that. I'm trying to be helpful. – Duncan C Dec 18 '16 at 15:51
  • You should still edit your question to show the code for your `fetchItems()` function, and you should accept my answer if it helped you. – Duncan C Dec 18 '16 at 15:52
  • And if it was helpful, an up-vote would be appreciated. Note that I added some print statements that illustrate the order in which this code is executed. I suggest adding those to your code and noting the output in the debugger. That should help you understand how async functions work. The key take-away is that an async function returns before it's task has completed, and runs the completion handler code that's passed to it at some future time once the task has completed. – Duncan C Dec 18 '16 at 15:56
-2

You should load the data of the list in the method of loadView, so that it will be loaded early before UITableView reads.

Sometimes, viewDidLoad performs a little bit slowly. Generally, people will initialize the data of list or sth in the method of loadView to make sure the data is completed before view is created.

Johnny
  • 1,112
  • 1
  • 13
  • 21