3

I have been having this error with my swift program, I am using PHP and MySQL as database. I am to display the data from database to tableview. it was working before but after a few times of running it to the emulator, I've been having the same error

class Home: UIViewController, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var btnaddclass: UIButton!
@IBOutlet var tableView: UITableView!
   var values: NSArray = []

func get(){
    let url = NSURL(string: "http://localhost/show_db.php")
    let data = NSData(contentsOf: url! as URL)
    values = try! JSONSerialization.jsonObject(with: data! as Data, options:JSONSerialization.ReadingOptions.mutableContainers)as! NSArray
    tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return values.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SpecialCell
    let maindata = values[indexPath.row] as! [String:AnyObject]

    cell.Lblclasstitle.text = maindata["class_title"] as? String
    cell.Lblschool.text = maindata["school"] as? String
    cell.Lblsubject.text = maindata["subject"] as? String
    return cell
}
   override func viewDidLoad() {
    self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
    get()


}
override func viewWillAppear(_ animated: Bool) {
    self.tableView.reloadData()
}
}
halfer
  • 19,824
  • 17
  • 99
  • 186
  • pretty similar, try [safe way: do try catch!](http://stackoverflow.com/questions/39516199/type-any-has-no-subscript-members-in-xcode-8-swift-3/39516303#39516303) – Bista Jan 15 '17 at 12:58
  • 1
    It happens when the returned data is not a valid JSON. Show text representation of the returned data. And one more, you should not call `NSData.init(contentsOf:)` in the main thread. – OOPer Jan 15 '17 at 13:02
  • So many `!` - crashes are expected behaviour for your coding style. – luk2302 Jan 15 '17 at 13:36

3 Answers3

9

As some of the other answers have noted, the issue is that you are forcing the JSON deserialization and assuming that it will always, 100% of the time, work. This is almost always not a good choice. There are two options to solve this:

1) Use try? instead

This can be a good choice if the statement that you are trying to perform fails, then you will obtain nil and then can do something based off of that. For example, you can change your get() function to:

func get(){
    //remove NS prefix and unwrap optional
    guard let url = URL(string: "http://localhost/show_db.php") else { return }

    //remove ! from url and unwrap optional
    guard let data = try? Data(contentsOf: url) else { return }

    //try to deserialize. The return value is of type Any
    guard let deserializedValues = try? JSONSerialization.jsonObject(with: data) else { return }

    //Convert to array of dictionaries
    guard let arrayOfDictionaryValues = deserializedValues as? [[String:String]] else { return }

    //If you make it here, that means everything is ok
    values = arrayOfDictionaryValues
    
    tableView.reloadData()
}

Notice that each of the guard statements gives you an opportunity to perform some action and return. Maybe you want to display a message stating there was a network error.

2) Use a do try catch block.

I think using try? is more appropriate for your case but a do try catch block can be appropriate in some cases. For example:

func getWithErrorHandling() {
    guard let url = URL(string: "http://localhost/show_db.php") else { return }

    do {
        let data = try Data(contentsOf: url)
        let deserializedValues = try JSONSerialization.jsonObject(with: data)
        guard let arrayOfDictionaryValues = deserializedValues as? [[String:String]] else { return }
        values = arrayOfDictionaryValues
    } catch {
        //You should separate the different catch blocks to handle the different types of errors that occur
        print("There was an error")
    }
}

There are other improvements that can be made to your overall approach but since you are a beginner (as you noted), it is best to learn one thing at a time.

Also, as others have mentioned, you should not get the data from the main thread. Use URLSession instead. This post is a good example:

Correctly Parsing JSON in Swift 3

Community
  • 1
  • 1
Guillermo Alvarez
  • 1,695
  • 2
  • 18
  • 23
5

1.Don't use NS... classes in Swift3. For example:

let url = URL(string: "http://localhost/show_db.php")
let data = Data(contentsOf: url!)

2.Use do-try-catch for exception.

do {
    try ...
} catch {
    // handle exception
}

3.Don't use as! unless you're sure which class it is. Use this:

if let array = some as? Array {
    // use array now
}
Lumialxk
  • 6,239
  • 6
  • 24
  • 47
  • great! it's a big help as I am just new to this. i tried your suggestions but the it outputted this issue : The data couldn’t be read because it isn’t in the correct format. – Enrique Jimmar C. Panti Jan 15 '17 at 13:39
  • @EnriqueJimmarC.Panti `JSONSerialization.jsonObject(...` only works for JSON format. – Lumialxk Jan 15 '17 at 13:41
  • @EnriqueJimmarC.Panti it might help if you print the data you are trying to deserialize to the console. My guess is the JSON is not valid coming from the server. – Guillermo Alvarez Jan 15 '17 at 13:47
1

Your code is full of problems.

The "try!" expression crashes if the code you are calling throws an exception. That is why you are crashing. Don't do that, especially in the handling of data from an external source. Use do { try.. } catch { } instead. There are tons of examples of swift try/catch blocks in Apple's documentation and online.

The error message you are getting is telling you what's wrong. You're getting garbage characters at the end of your JSON data stream. Convert your data stream to a string and log it.

Don't use as! either, unless you're positive that the object can be cast to the desired type. (Force-casts crash when they fail.)

As @OOPer says in their comment, you should not be loading data from an URL on the main thread. That locks up the UI until the load is done, and may cause your app to be killed if the load takes too long. You should use an async method like URLSession.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • I just copied it from a tutorial. haha, thanks @Duncan C, I'm not pretty familiar with async method. I'm just new to this. can you tell me more about it? – Enrique Jimmar C. Panti Jan 15 '17 at 13:55
  • Take a look at my answer to this question: http://stackoverflow.com/questions/41262793/swift-wait-for-firebase-to-load-before-return-a-function/41262940#41262940 – Duncan C Jan 15 '17 at 14:10
  • If you copied that code from a tutorial you should find another tutorial. That code is like a minefield of bad coding habits and potential crashes. Can you post a link to the tutorial? – Duncan C Jan 15 '17 at 14:13