1

I have been trying to integrate a Pinboard bookmarks view (by parsing an RSS Feed and displaying it in a UITableView) in my browser app, and I'm encountering a few issues. You can see my previous questions here and here. I'm trying to use the variables I collected from my user in the Alert, to get the user's secret RSS feed token. However when I run the app, it causes a crash with a error of "EXC_BAD_INSTRUCTION" with the following console output:

fatal error: unexpectedly found nil while unwrapping an Optional value

I checked everything but I can't find a nil.

This is the code I'm using:

class SettingsTableViewController: UITableViewController, MFMailComposeViewControllerDelegate, NSXMLParserDelegate {

var _pinboardUsername: String?
var _pinboardAPIToken: String?

var parser: NSXMLParser = NSXMLParser()
var bookmarks: [Bookmark] = []
var pinboardSecret: String = String()
var eName: String = String()

...

    @IBAction func pinboardUserDetailsRequestAlert(sender: AnyObject) {
    //Create the AlertController
    var pinboardUsernameField :UITextField?
    var pinboardAPITokenField :UITextField?
    let pinboardUserDetailsSheetController: UIAlertController = UIAlertController(title: "Pinboard Details", message: "Please enter your Pinboard Username and API Token to access your bookmarks", preferredStyle: .Alert)
    //Add a text field
    pinboardUserDetailsSheetController.addTextFieldWithConfigurationHandler({(usernameField: UITextField!) in
        usernameField.placeholder = "Username"
        usernameField.text = self._pinboardUsername
        var parent = self.presentingViewController as! ViewController
        pinboardUsernameField = usernameField

    })
    pinboardUserDetailsSheetController.addTextFieldWithConfigurationHandler({(apiTokenField: UITextField!) in
        apiTokenField.placeholder = "API Token"
        apiTokenField.text = self._pinboardAPIToken
        var parent = self.presentingViewController as! ViewController
        pinboardAPITokenField = apiTokenField

    })
    pinboardUserDetailsSheetController.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
    pinboardUserDetailsSheetController.addAction(UIAlertAction(title: "Done", style: .Default, handler: { (action) -> Void in
        // Now do whatever you want with inputTextField (remember to unwrap the optional)
        var valueUser = pinboardUsernameField?.text
        NSUserDefaults.standardUserDefaults().setValue(valueUser, forKey: AppDefaultKeys.PinboardUsername.rawValue)
        var valueAPI = pinboardAPITokenField?.text
        NSUserDefaults.standardUserDefaults().setValue(valueAPI, forKey: AppDefaultKeys.PinboardAPIToken.rawValue)
        let url:NSURL = NSURL(string: "https://api.pinboard.in/v1/user/secret/?auth_token=\(valueAPI)")!
        self.parser = NSXMLParser(contentsOfURL: url)!
        self.parser.delegate = self
        self.parser.parse()

        func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject]) {
            self.eName = elementName
            if elementName == "result" {
                self.pinboardSecret = String()
                NSUserDefaults.standardUserDefaults().setValue(self.pinboardSecret, forKey: AppDefaultKeys.PinboardSecret.rawValue)
            }
        }
    }))
    self.presentViewController(pinboardUserDetailsSheetController, animated: true, completion: nil)   
}

The error is in the let url: NSURL = ... line of the parser function.

What am I doing wrong?

Community
  • 1
  • 1
PastaCoder
  • 1,031
  • 1
  • 9
  • 16

2 Answers2

2

The issue is that valueAPI is an optional. If, for example, valueAPI was foo, your URL string would look like:

https://api.pinboard.in/v1/user/secret/?auth_token=Optional("foo")

I'd suggest building your URL string first, looking at it, and you'll see what I mean.

Bottom line, that Optional("...") reference in the URL is not valid and thus instantiating the URL with that string will fail. And using the ! to unwrap that optional NSURL will crash as you outlined.

You have to unwrap the valueAPI optional before using it in the URL string.

let url:NSURL = NSURL(string: "https://api.pinboard.in/v1/user/secret/?auth_token=\(valueAPI!)")!

Or you could make valueAPI an implicitly unwrapped optional.

Frankly, if there's any chance that valueAPI might be nil, I'd rather use optional binding (i.e. if let clauses), so you could gracefully detect these issues in the future.


Regarding the completely separate XML parsing problems, there are a couple of issues:

  1. I'd suggest retrieving the response asynchronously via NSURLSession. Using NSXMLParser with contentsOfURL performs the request synchronously which results in a poor UX and the iOS watchdog process may kill your app.

    Instead, use NSURLSession method dataTaskWithURL and then use the resulting NSData with the NSXMLParser object instead.

  2. If you do that, you can also convert the NSData response object to a string and examine it. Often if there are API problems, the response will include some description of the nature of the error (sometimes it's text, sometimes it's HTML, sometimes it's XML; depends upon how that web service was designed). If you don't get the XML response you expected, you really will want to look at the raw response to identify what's wrong.

  3. Your NSXMLParserDelegate method didStartElement is defined inside the addAction block. It must be a method of the class, itself, not buried inside this closure.

  4. Your didStartElement is saving the secret. But you don't have the secret yet, so there's no point in trying to save it yet.

  5. I don't see the other NSXMLParserDelegate methods (foundCharacters, didEndElement, and parseErrorOccurred, at the very least). Do you have those defined elsewhere? See https://stackoverflow.com/a/27104883/1271826

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hi @Rob, this is a good point indeed. However, even when I unwrap the optional with a ! it still isn't working. I'm using the Pinboard API as specified [here](https://pinboard.in/api), which should only return the secret in a result element name: `$ curl https://api.pinboard.in/v1/user/secret/?auth_token=user:TOKEN 6493a84f72d86e7de130`, How can I be sure it got it correctly? When I switch the variable with the token itself it works with curl but it won't work with Xcode and the code. – PastaCoder Apr 26 '15 at 20:13
  • As an addition to my previous comment, I think it's something with the eName (since it works with curl on terminal and not in the code), but I'm not sure what... – PastaCoder Apr 26 '15 at 20:17
  • "it still isn't working". So, what is it doing? When you look at the URL string, does it look ok? Or is the app still crashing? If it's not crashing, what is it returning in response to your query? Frankly, if we answered your question about crashing, we should close this question and you should post a new question about how to use this pinboard API. And, frankly, that question seems better suited to the [pinboard-dev Google group](http://groups.google.com/group/pinboard-dev) rather than Stack Overflow. – Rob Apr 26 '15 at 20:23
  • @Rob "It still isn't working" meaning it still doesn't return anything as the pinboardSecret variable. The URL and everything looks ok, and I'm not getting any errors. My question was about using NSXMLParser and the crash, and the issue with NSXMLParser is yet to be answered. – PastaCoder Apr 26 '15 at 20:37
  • The crashing due to the `nil` URL stemming from the invalid URL is a different question from the parsing problem. Please don't combine completely different questions in the future. But, setting that aside, there are three problems: (a) you really should examine what the server sent back before you go too far in the parsing exercise and make sure the response looks OK; (b) your `NSXMLParserDelegate` methods must be methods of the class, not buried within the closure; (c) you're looking for the secret in `didStartElement`, and you really have to implement the other delegate methods, too. – Rob Apr 26 '15 at 21:10
  • @Rob Ok, Thanks for your edit and suggested solution, I'll try it and report the results shortly. – PastaCoder Apr 27 '15 at 18:25
  • 1
    @Rob I forgot to list the other delegate methods but they are there, I did some testing, and moved them from the addAction block to a block of their own blocks after viewDidLoad() as per your suggestion: _3. Your `NSXMLParserDelegate` method `didStartElement` is defined inside the `addAction` block. It must be a method of the class, itself, not buried inside this closure._ and now it is working correctly. I will also try using your other suggestions to improve the code for a better UX. So Thanks for your answer :) I'll be marking your answer as correct. :) – PastaCoder Apr 27 '15 at 18:57
1
let url:NSURL = NSURL(string: "https://api.pinboard.in/v1/user/secret/?auth_token=\(valueAPI)")!

NSURL() is a failable initializer, meaning it may fail on initialization (malformed URL, server not responding, ...).

By forcing the unwrap with ! you declare that you are absolutely sure to get a valid object, but you don't. This leads to the fatal error.

zisoft
  • 22,770
  • 10
  • 62
  • 73
  • Hi Zisoft, Thanks for the answer, while the app doesn't crash now, I am not getting my pinboardSecret filled up, which is the whole point of the parser. – PastaCoder Apr 26 '15 at 19:59