Nadz
Nadz

Reputation: 21

Is there a way for asynchronous task to be completed first in Swift 3 Xcode 8?

I am very new in swift programming and I am not that familiar with Web Services. I want to create an iOS mobile application for poems.

I was able to retrieve the json encoded data but my problem is I can't transfer it to my tableview here. When I ran it, I think the problem was because of the asynchronous task. Since it is in asynchronous task, the delegated functions of the tableview (numberOfRows) is executed when name.count is still 0, because the task would just run after the delegated func is executed. That's why I can't see my data in my TableView... I hope someone can help me here. I have googled and tried Completion Handlers (which I have no idea what it's used for) and I tried changing it to Synchronous which lead me to errors.. Thank you very much!!!

import UIKit
import Foundation

class TimelineViewController: UIViewController  {
    @IBOutlet weak var contentTableView: UITableView!
    var name = [String]()
    var bio = [String]()

    @IBOutlet weak var oPostBtn: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        getPoems()
    }

    func getPoems()
    {
        let url:NSURL = NSURL(string: "http://192.168.1.6/Test/feed1.php")!
        let request:NSMutableURLRequest = NSMutableURLRequest(url:url as URL)

        request.httpMethod = "GET"

        NSURLConnection.sendAsynchronousRequest(request as URLRequest, queue: OperationQueue.main){(response, data, error) in }

        let task = URLSession.shared.dataTask(with: request as URLRequest) {
                data, response, error in

                if error != nil {
                    print("Error = \(error)")
                    return
                }
                do {
                    print("Response=\(response)")

                    let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
                    print("Response data = \(responseString)")

                    //Converting response to NSDictionary
                    var json: NSDictionary!
                    json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary

                    //getting the JSONArray poems from the response
                    let returnPoems: NSArray = json["returnPoems"] as! NSArray

                    print(returnPoems);


                    //looping through all the json objects in the array teams
                    for i in 0 ..< returnPoems.count{


                        let aObject = returnPoems[i] as! [String : AnyObject]


                        self.name.append(aObject["fullName"] as! String)
                        self.bio.append(aObject["Bio"] as! String)
                        //displaying the data

                        print("Fullname -> ", self.name)
                        print("Bio ->", self.bio)

                        print("===================")
                        print("")

                    }


                }
                catch {
                    print("ERROR: \(error)")
                }

        }
        task.resume()

    }

}

extension TimelineViewController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {


        let cell = contentTableView.dequeueReusableCell(withIdentifier: "PoemCell")


        cell?.textLabel?.text = name[indexPath.row]
        cell?.detailTextLabel?.text = bio[indexPath.row]

        return cell!
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return name.count
    }
    func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        print("You selected cell #\(indexPath.row)!")

        let indexPath = tableView.indexPathForSelectedRow!
        let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell

    } 
}

Upvotes: 0

Views: 621

Answers (1)

Rob
Rob

Reputation: 438122

In your completion block, when you're done building the name and bio arrays, refresh the table by calling reloadData from the main queue:

DispatchQueue.main.async { 
    self.tableView.reloadData() 
}

There were a few other things I'd suggest:

  • You should remove that NSURLConnection code. It's performing redundant request and you're not doing anything with the response; plus it is a deprecated API anyway and should be removed;

  • You should use URL instead of NSURL;

  • You should use URLRequest instead of NSMutableURLRequest;

  • You should use String rather than NSString;

  • You should use Swift Array and Dictionary rather than NSArray and NSDictionary;

  • Your signature for didSelectRowAt is incorrect. Use IndexPath, not NSIndexPath.

  • You are updating the name and bio arrays from the URLSession background queue. That is not thread-safe. You can solve this by updating these from the main queue to avoid needing to do additional synchronization.

  • I'd even suggest getting rid of those two different arrays and have a single array of a custom object, Poem. This makes code much simpler. It opens up new possibilities, too (e.g. what if you want to sort the name array ... how would you update the separate bio array accordingly; use a single custom object and this problem goes away).

Thus:

struct Poem {
    let name: String
    let bio: String

    init?(dictionary: [String: Any]) {
        guard let name = dictionary["fullName"] as? String,
            let bio = dictionary["Bio"] as? String else {
                print("Did not find fullName/Bio")
                return nil
        }

        self.name = name
        self.bio = bio
    }
}

class TimelineViewController: UIViewController {

    @IBOutlet weak var contentTableView: UITableView!

    var poems = [Poem]()

    @IBOutlet weak var oPostBtn: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        getPoems()
    }

    func getPoems() {
        let url = URL(string: "http://192.168.1.6/Test/feed1.php")!
        var request = URLRequest(url: url)

        request.httpMethod = "GET"

        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data, error == nil else {
                print("Error = \(error?.localizedDescription ?? "Unknown error")")
                return
            }

            if let response = response {
                print("Response=\(response)")
            }

            if let responseString = String(data: data, encoding: .utf8) {
                print("Response data = \(responseString)")
            }

            // Converting response to Dictionary

            guard let json = try? JSONSerialization.jsonObject(with: data),
                let responseObject = json as? [String: Any],
                let returnPoems = responseObject["returnPoems"] as? [[String: Any]] else {
                    print("did not find returnPoems")
                    return
            }

            print(returnPoems)

            // dispatch the update of model and UI to the main queue

            DispatchQueue.main.async {
                self.poems = returnPoems.flatMap { Poem(dictionary: $0) }
                self.contentTableView.reloadData()
            }
        }

        task.resume()
    }

}

extension TimelineViewController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = contentTableView.dequeueReusableCell(withIdentifier: "PoemCell", for: indexPath)

        let poem = poems[indexPath.row]

        cell.textLabel?.text = poem.name
        cell.detailTextLabel?.text = poem.bio

        return cell
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return poems.count
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You selected cell #\(indexPath.row)!")

        // let indexPath = tableView.indexPathForSelectedRow!                         // why do this? ... the indexPath was passed to this function
        // let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell  // why do this? ... you don't care about the cell ... go back to you model

        let poem = poems[indexPath.row]

        // do something with poem, e.g.

        print(poem)
    }
}

Upvotes: 1

Related Questions