-1

For one week I have been trying to get a string returned from dataTask().

I already read a lot here on StackOverFlow and also from serval sites where they tackle this topic. For example, this one. So I already understand that it's that the dataTask doesn't directly return values, cause it happens on different threads and so on. I also read about closures and completion handlers. I really got the feeling that I actually already got a little clue what this is about. But I can't get it to work.

So this is my code. I just post the whole code so no-one needs to worry that the problem sticks in a part which I don't show. Everything is working fine until I try to return a value and save it for example in a variable:

func requestOGD(code gtin: String, completion: @escaping (_ result: String) -> String) {
    // MARK: Properties
    var answerList: [String.SubSequence] = []
    var answerDic: [String:String] = [:]
    var product_name = String()
    var producer = String()

    // Set up the URL request
    let ogdAPI = String("http://opengtindb.org/?ean=\(gtin)&cmd=query&queryid=400000000")

    guard let url = URL(string: ogdAPI) else {
        print("Error: cannot create URL")
        return
    }

    let urlRequest = URLRequest(url: url)
    // set up the session
    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)

    // make the request
    let task = session.dataTask(with: urlRequest) {
        (data, response, error) in
        // check for any errors
        guard error == nil else {
            print("error calling GET on /todos/1")
            print(error!)
            return
        }
        // make sure we got data
        guard let responseData = data else {
            print("Error: did not receive data")
            return
        }

        // parse the result, which is String. It willbecome split and placed in a dictionary
        do {
            let answer = (String(decoding: responseData, as: UTF8.self))

            answerList = answer.split(separator: "\n")

            for entry in answerList {
                let entry1 = entry.split(separator: "=")
                if entry1.count > 1 {
                    let foo = String(entry1[0])
                    let bar = String(entry1[1])
                    answerDic[foo] = "\(bar)"
                }
            }

            if answerDic["error"] == "0" {
                product_name = answerDic["detailname"]!
                producer = answerDic["vendor"]!

                completion(product_name)
            } else {
                print("Error-Code der Seite lautet: \(String(describing: answerDic["error"]))")
                return
            }
        }
    }
    task.resume()

Here I call my function, and no worries, I also tried to directly return it to the var foo, also doesn't work The value only exists within the closure:

    // Configure the cell...
    var foo:String = ""

    requestOGD(code: listOfCodes[indexPath.row]) { (result: String) in
        print(result)
        foo = result
        return result
    }

    print("Foo:", foo)

    cell.textLabel?.text = self.listOfCodes[indexPath.row] + ""

    return cell
}

So my problem is, I have the feeling, that I'm not able to get a value out of a http-request.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Anthony
  • 904
  • 1
  • 8
  • 15
  • Hey @matt thanks for your answer. Yeah, that's what I also understood, but I thought the solution of this is using a completion handler? Isn't it? I mean, it's simple scenario: I'm requesting an database API by http-request. The API responds with a string. No I want to save the string. How do I do that? – Anthony Jun 09 '18 at 15:32
  • You should not be attempting to populate each cell with individually obtained remote data. Do one remote access to obtain all of the data for the whole table view (assuming it's a reasonable amount of data). Package up the obtained data into an appropriate data model for the table view, and then reload the table view. Now each cell is populated with the local data model that was loaded just once. – rmaddy Jun 09 '18 at 16:52
  • @maddy thanks 4 ur answer. That is what I would love to do. Just do a request, save the string somewhere in my application, do my logics with it and use it to for example pass the single data into a TableView. BUT, can you tell me, how? Is my completion handler wrong? Do I need to chance my whole application structure? I really getting crazy over this, cause today it should be such a normal task. I would be happy, if I can just can make a simpel call. Is there any library or tutorial you can recommend? – Anthony Jun 09 '18 at 17:56

1 Answers1

1

You used a completion handler in your call to requestOGD:

requestOGD(code: listOfCodes[indexPath.row]) {
    (result: String) in
    // result comes back here
}

But then you tried to capture and return that result:

    foo = result
    return result

So you're making the same mistake here that you tried to avoid making by having the completion handler in the first place. The call to that completion handler is itself asynchronous. So you face the same issue again. If you want to extract result at this point, you would need another completion handler.

To put it in simple terms, this is the order of operations:

requestOGD(code: listOfCodes[indexPath.row]) {
    (result: String) in
    foo = result // 2
}
print("Foo:", foo) // 1

You are printing foo before the asynchronous code runs and has a chance to set foo in the first place.

In the larger context: You cannot use any asynchronously gathered material in cellForRowAt. The cell is returned before the information is gathered. That's what asynchronous means. You can't work around that by piling on further levels of asynchronicity. You have to change your entire strategy.

matt
  • 515,959
  • 87
  • 875
  • 1,141