0

I have a web page for giving me the server data in JSON format and I am trying to to reach the site and get these objects. But the data will return with 0; empty variable. My code like this.

    func downloadItems() {

    let url: URL = URL(string: urlPath)!

    let defaultSession = URLSession(configuration: URLSessionConfiguration.default)

    let task = defaultSession.dataTask(with: url) {
        (data, response, error) in

        if error != nil {
            print("Failed to download data")
        }else {
            let httpResponse = response as? HTTPURLResponse
            print("data is like: \(data as! NSData)")
            print(httpResponse)
            self.data = data!
            print("Data downloaded")
            self.parseJSON(self.data)
        }

    }

    task.resume()
}

func parseJSON(_ data: Data){
    var jsonResult = NSArray()

    do{
        jsonResult = try JSONSerialization.jsonObject(with: data, options: []) as! NSArray
        print(jsonResult)
    } catch let error as NSError {
        print(error)
    }

    var jsonElement = NSDictionary()
    let forest = NSMutableArray()

    for tree in 0 ..< jsonResult.count {

        jsonElement = jsonResult[tree] as! NSDictionary

        let tempTree = trees()

        if let latinName = jsonElement["latin_name"] as? String,
        let turkishName = jsonElement["turkish_name"] as? String,
        let seedType = jsonElement["seed_type"] as? Int,
        let leafType = jsonElement["leaf_type"] as? Int,
        let spreadingArea = jsonElement["spreading_area"] as? String,
        let bothanicalProp = jsonElement["bothanical_prop"] as? String {
            tempTree.latin_name = latinName
            tempTree.turkish_name = turkishName
            tempTree.seed_type = seedType
            tempTree.leaf_type = leafType
            tempTree.spreading_area = spreadingArea
            tempTree.botanical_prop = bothanicalProp
        }

        forest.add(tempTree)

    }

    DispatchQueue.main.async {
        self.delegate.itemsDownloaded(items: forest)
    }

}

and my main view controller is like that

    override func viewDidLoad() {
    super.viewDidLoad()

    //table view delegats and datasource declaration
    treeTableView.delegate = self
    treeTableView.dataSource = self

    let treeModel = trees()
    treeModel.delegate = self
    treeModel.downloadItems(completion: <#T##(Data?) -> Void#>)

}

func itemsDownloaded(items: NSArray) {
    feedTrees = items
    self.treeTableView.reloadData()
}

When I enter the site the JSON object is here. But when I tried to retrieve the object with dataTask. Data will be 0. Also HTTP Response is 200. Is there any knowledge about this situation ? Thanks and good coding.

print(jsonResult) is

{
    "bothanical_prop" = "T\U00fcrkiyede park ve bah\U00e7elerde dekoratif ama\U00e7la kullan\U0131l\U0131r. Do\U011fal yay\U0131l\U0131\U015f alan\U0131 \U015eili'nin g\U00fcneyi ve G\U00fcnaybat\U0131 Arjantin'deki And Da\U011flar\U0131d\U0131r.";
    "latin_name" = "Araucaria araucana (Molina) K. Koch.";
    "leaf_type" = 1;
    "seed_type" = 1;
    "spreading_area" = "Do\U011fal alanlarda erkekler 15-18 m, di\U015filer ise 30-50 m ye kadar boylanabilir. Park ve bah\U00e7elerde ise 10 m'yi nadiren ge\U00e7en herdemye\U015fil a\U011fa\U00e7lard\U0131r. G\U00f6zvdesi d\U00fczg\U00fcnd\U00fcr; \U00f6zellikle gen\U00e7 bireyleri simetriktir ve konik formludur. Yapraklar\U0131, deri gibi sert, u\U0308\U00e7gen \U015feklinde, u\U00e7lar\U0131 sivri, bat\U0131c\U0131 ve her iki yu\U0308zu\U0308 koyu ye\U015fildir.";
    "turkis_name" = "Maynum \U00c7\U0131kmaz A\U011fac\U0131";
},
    {
    "bothanical_prop" = "Norfolk adalar\U0131na (Avustralya) \U00f6zgu\U0308 endemik bir tu\U0308rdu\U0308r. Dekoratif ama\U00e7l\U0131 \U00fclkemizde Ege, Akdeniz ve Marmara \U00e7evresinde d\U0131\U00e7 mekan bitkisi olarak kullan\U0131l\U0131r.";
    "latin_name" = "Araucaria heterophylla (Salisb.) Franco";
    "leaf_type" = 1;
    "seed_type" = 1;
    "spreading_area" = "60-70 m\U2019ye kadar boylanabilen bir orman a\U011fac\U0131d\U0131r. Dallar g\U00f6vdeden 4-7\U2019li \U00e7evrel olarak \U00e7\U0131kar. G\U00f6vde kabu\U011fu ince levhalar halinde \U00e7atlakl\U0131d\U0131r. Yapraklar\U0131 2 farkl\U0131 \U015fekildedir: Gen\U00e7 ve yan su\U0308rgu\U0308nlerde a\U00e7\U0131k ye\U015fil, biz gibi uzun, k\U00f6\U015feli ve yumu\U015fak; ya\U015fl\U0131 su\U0308rgu\U0308nlerde ise daha k\U0131sa, daha sert, s\U0131k, birbiri u\U0308zerine binmi\U015f ve u\U00e7 k\U0131s\U0131mlar\U0131 boynuz gibi \U00f6ne do\U011fru k\U0131vr\U0131lm\U0131\U015f olan yapraklar vard\U0131r";
    "turkis_name" = "Salon arokaryas\U0131";
}
  • Since you get a 200 response, but cannot retrieve data, maybe there is a mistake in your parseJSON method? I feel like there is something wrong with the way you try to parse data. – emrepun Oct 01 '18 at 23:16
  • Many API servers return unexpected results with status code 200, so that cannot be a useful info. What do you get when you put `print(data! as NSData)` before your `print("Data downloaded")` ? – OOPer Oct 02 '18 at 00:56
  • "Data will be 0." How are you calling this function? Post the code that calls this function and tell us why you think your "data is 0." – Duncan C Oct 02 '18 at 03:36
  • @OOPer when I wrote your suggestion I faced it that hell `<5b7b226c 6174696e 5f6e616d 65223a22 41726175 63617269 61206172 61756361 6e612028 4d6f6c69 6e612920...` its goes like that I do not know what is this. – Mertalp Tasdelen Oct 02 '18 at 07:23
  • That represents a String `[{"latin_name":"Araucaria araucana (Molina) ...`. Facts, **#1** the API server returns non-nil, non-empty `data`. **#2** the content of the `data` seems to be a valid(?) JSON. Hypothese: **#1** you are accessing the `data` before it is ready. **#2** your `parseJSON(_:)` silently ignoring some errors, so you cannot distinguish between empty `data` and invalid JSON. Anyway, how did you check your `data` other than the way I have shown? – OOPer Oct 02 '18 at 07:42
  • Your updated code is not consistent. The method `downloadItems()` has no arguments, but you call it as `downloadItems(completion: <#T##(Data?) -> Void#>)`. You say you have a code like `DispatchQueue.main.async { self.delegate.itemsDownloaded(items: forest) }`, but I cannot find it anywhere. If you really want your issue to be solved soon, show your current code as is, and tell us how you checked that _data will return with 0; empty variable_. – OOPer Oct 02 '18 at 13:34
  • @OOPer I did edited the code can you look it please – Mertalp Tasdelen Oct 02 '18 at 13:48
  • Some parts are still missing or mismatching, but I have found one point to check. What do you get when you put `print(jsonElement)` before the line `let tempTree = trees()` ? (If it's long, please add the result into your question. Comments are hard to read.) – OOPer Oct 02 '18 at 14:06
  • I edit the question @OOPer – Mertalp Tasdelen Oct 02 '18 at 19:23

4 Answers4

1

If you call it and then expect data to be non-empty you will be disappointed. A URLSession dataTask is asynchronous. It won't have read your data when the downloadItems() function returns.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • I know that the dataTask İs asynchronous. In my code I have the line for that like this `DispatchQueue.main.async { self.delegate.itemsDownloaded(items: forest) }` – Mertalp Tasdelen Oct 02 '18 at 07:31
  • @MertalpTaşdelen that code is intended for asynchronous thread, not for asynchronous request, check my answer. – Andrew21111 Oct 02 '18 at 07:33
  • Mertalp, what you said about `DispatchQueue.main.async` makes no sense. That is not some magic incantation that makes async code synchronous. See Andrew's answer. You need to pass a completion handler to downloadItems, and use the result in the completion handler. – Duncan C Oct 02 '18 at 10:18
0

URLSession dataTask is asynchronous, so you have to make a callback to get your data.

Change you code this way:

func downloadItems(completion: @escaping (_ result: Data?)->Void) {

  let url: URL = URL(string: urlPath)!
  let defaultSession = URLSession(configuration: URLSessionConfiguration.default)
  let task = defaultSession.dataTask(with: url) { (data, response, error) in
    if error != nil {
      print("Failed to download data")
      completion(nil)
    } else {
      let httpResponse = response as? HTTPURLResponse
      print(httpResponse)
      // remove this
      //self.data = data!
      //print("Data downloaded")
      //self.parseJSON(self.data)

      //add this
      guard let data = data else { return }
      completion(data)
    }
  }
  task.resume()
}

Then you can call your method anywhere this way:

downloadItems{ data in
 self.data = self.parseJSON(data)
}

EDIT: So in your case you have to change this:

treeModel.downloadItems(completion: <#T##(Data?) -> Void#>)

into this:

treeModel.downloadItems{ data in
    feedTrees = self.parseJSON(data)
}

I say this because you're parseJSON function does return nothing, so I assume you're parseJSON is wrong

I would do it this way:

NOTE: this function assume that your feedTrees is NSMutableArray

//Make your function return NSMutable array
func parseJSON(_ data: Data) -> NSMutableArray{
   var jsonResult = NSArray()
   //Put all of your code into do-catch
do{
    jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! NSArray

var jsonElement = NSDictionary()
let forest = NSMutableArray()

for tree in 0 ..< jsonResult.count {

    jsonElement = jsonResult[tree] as! NSDictionary

    let tempTree = trees()

    if let latinName = jsonElement["latin_name"] as? String,
    let turkishName = jsonElement["turkish_name"] as? String,
    let seedType = jsonElement["seed_type"] as? Int,
    let leafType = jsonElement["leaf_type"] as? Int,
    let spreadingArea = jsonElement["spreading_area"] as? String,
    let bothanicalProp = jsonElement["bothanical_prop"] as? String {
        tempTree.latin_name = latinName
        tempTree.turkish_name = turkishName
        tempTree.seed_type = seedType
        tempTree.leaf_type = leafType
        tempTree.spreading_area = spreadingArea
        tempTree.botanical_prop = bothanicalProp

        //Add here your tree or it can be empty
        forest.add(tempTree)
    }

}

 //AFTER THE LOOP
 return forest

} catch let error as NSError {
    print(error)
}

}
Zonker.in.Geneva
  • 1,389
  • 11
  • 19
Andrew21111
  • 868
  • 8
  • 17
  • there is a error in guard let line like this "'guard' body must not fall through, consider using a 'return' or 'throw' to exit the scope" – Mertalp Tasdelen Oct 02 '18 at 07:55
  • My bad, change completion(nil) with return and add the completion(nil) in the error if, check again, I updated the answer – Andrew21111 Oct 02 '18 at 08:05
  • how can I use the downloadItems(completion: ) in my main view controller ? and also my I am using the parseJSON method. I will edit the my question now – Mertalp Tasdelen Oct 02 '18 at 10:40
0

Seems your API returns turkis_name instead of turkish_name:

"turkis_name" = "Maynum \U00c7\U0131kmaz A\U011fac\U0131";

Tell your server side engineers fix it to follow the API spec. Or, is that you who mis-read the API spec?


Anyway, when you need to adapt your code to the actual response, you may need to fix only one line.

    let turkishName = jsonElement["turkis_name"] as? String,

But I give you a practically useful advice:

Do not ignore errors or invalid inputs silently.

With some modifications to make your code more Swifty, your parseJSON(_:) would be something like below:

func parseJSON(_ data: Data) -> NSArray {
    do{
        guard let jsonResult = try JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
            print("Invalid JSON structure")
            return []
        }

        let forest = NSMutableArray()

        for jsonElement in jsonResult {
            if
                let latinName = jsonElement["latin_name"] as? String,
                let turkishName = jsonElement["turkis_name"] as? String, //###Use the right key here
                let seedType = jsonElement["seed_type"] as? Int,
                let leafType = jsonElement["leaf_type"] as? Int,
                let spreadingArea = jsonElement["spreading_area"] as? String,
                let bothanicalProp = jsonElement["bothanical_prop"] as? String
            {
                let tempTree = trees()

                tempTree.latin_name = latinName
                tempTree.turkish_name = turkishName
                tempTree.seed_type = seedType
                tempTree.leaf_type = leafType
                tempTree.spreading_area = spreadingArea
                tempTree.botanical_prop = bothanicalProp

                forest.add(tempTree)
            } else {
                //### Do not ignore errors or invalid inputs silently.
                print("Missing any of 'latin_name', 'turkis_name', 'seed_type', 'leaf_type', 'spreading_area', 'bothanical_prop'", jsonElement)
            }
        }
        return forest

    } catch let error {
        print(error)
        return []
    }
}

(There are some more parts I want to fix, but those needs modification of the hidden parts of your code, so I have given up. And consider adopting Codable, when working with JSON.)


You may need some more fixes, but when you write something about unexpected result, please remember to include:

  • Concrete output of the actual result
  • In addition to where and when you get the output
  • And the expected result

Putting print statement and showing the output with the code including it would be a good way.

OOPer
  • 47,149
  • 6
  • 107
  • 142
0

I fix the issue... My code before the fix

override func viewDidLoad() {
    super.viewDidLoad()

    //table view delegats and datasource declaration
    self.treeTableView.delegate = self
    self.treeTableView.dataSource = self

    let treeModel = TreeModel()
    treeModel.delegate = self
    treeModel.downloadItems()

}
    func itemsDownloaded(items: NSArray) {
    feedTrees = items
    self.treeTableView.reloadData()
}

After fix the issue like that

    func itemsDownloaded(items: NSArray) {
    feedTrees = items
    self.treeTableView.reloadData()
}
override func viewDidLoad() {
    super.viewDidLoad()

    //table view delegats and datasource declaration
    self.treeTableView.delegate = self
    self.treeTableView.dataSource = self

    let treeModel = TreeModel()
    treeModel.delegate = self
    treeModel.downloadItems()

}

Just move the protocol method top of the viewDidLoad().