NSXMLDelegate in Swift issues

1k views Asked by At

I am trying to implement an NSXMLParserDelegate in Swift but I am having issues using the attributes dictionary in some of the delegate methods. For instance, in parser:didStartElement::

func parser(parser: NSXMLParser!, didStartElement elementName: String!, namespaceURI: String!, qualifiedName qName: String!, attributes attributeDict: [NSObject : AnyObject]!) {

trying to access attributeDict values like:

if self.feedType == FeedType.RSS {
    self.insideLink = true
} else {
    if (String(attributeDict["rel"]) == "alternate") && (String(attributeDict["type"]) == "text/html") {
        self.link = String(attributeDict["href"])
        self.insideLink = false
        self.doneWithLink = true
    }
}

I get an error message: "'String' is not convertible to 'FeedType'".

enum FeedType:String {

    case ATOM = "feed"
    case RSS = "rss"

}

class FeedParser:NSObject, NSXMLParserDelegate {

    var feedType:FeedType?

Is an error. Same for a dozen other variations on the theme... any insights?

I guess the question is how to properly use the key / value pairs in attributeDict which are of type [NSObject: AnyObject]?

1

There are 1 answers

1
rickster On BEST ANSWER

Looks like you might have a bogus error message: I'd recommend filing a bug.

I guess the question is how to properly use the key / value pairs in attributeDict which are of type [NSObject: AnyObject]

You're right: this looks like the root of the problem. The solution is not to construct a String from the dictionary lookup, but to (conditionally) cast to String. That is, rather than try to create a new String from a reference of indeterminate (and optional) type, ask Swift if that reference is actually to something that can be interpreted as a String:

let rel = attributeDict["rel"] as? String

That gets you an optional, both because attributeDict might not contain a value for the key rel, and because a conditional cast can fail (attributeDict["rel"] might be a value of a different type). Because you need to check two conditional casts and their unwrapped values, you could end up with a lot of force-unwrap operators (!) or deeply nested if-let blocks... that'd get ugly real fast.

Instead, you can take advantage of the fact that Optional is an enum. Its definition looks something like this:

enum Optional<T> {
    case None    // aka nil
    case Some(T) // what you get when you unwrap a non-nil optional
}

With that, you can use pattern matching to do the lookup, cast, unwrap and test safely and concisely:

switch (attributeDict["rel"] as? String, attributeDict["type"] as? String) {
  case let (.Some(rel), .Some(type)) where rel == "alternate" && type == "text/html":
    // harvest link
  default:
    // no match... fall back to other behavior, log an error, etc
}

Note that on the "harvest link" step you'll still need to conditionally cast and unwrap the attributeDict["href"] lookup. You could do that by nesting an if-let construct in that case, or by adding a third lookup to the switch tuple.


By the way, all the self. notation is unnecessary in Swift (unless you're in a closure or need to disambiguate method parameters from instance properties).