XMLParser in Swift 3

What belows I defined a subclass "MusicXMLParser" inherited from "XMLParser" (https://developer.apple.com/reference/foundation/xmlparser)


import Cocoa

class MusicXMLParser: XMLParser {

}



Then I created MusixXMLParser object using XMLParser's convenience initializer

let inputPath = "file:///~/Downloads/test1.xml"

let inputURL = URL(fileURLWithPath: inputPath)

var musicXMLParser = MusicXMLParser(contentsOf: inputURL)


Then I got run time error EXC_BAD_INSTRUCTION (code=EXC_I386INVOP, subcode=0x0)


Why? I did not implement anything extra in the definition of MusicXMLParser, theoretically it should just behave like XMLParse, except with a different class name.


If I change the last line of the code to be

var musicXMLParser = XMLParser(contentsOf: inputURL)


Then everything is OK. Could some expalin this strange thing?

Replies

Then I got run time error

EXC_BAD_INSTRUCTION (code=EXC_I386INVOP,
subcode=0x0)

This usually means that you’ve hit one of Swift’s runtime checks, typically when you try to access an

Optional
value that’s nil. What’s happening in this case is that your subclass of
XMLParser
is fetching the contents of the file, getting nothing back (for reasons I’ll explain below), then trapping when it tries to call the
XMLParser.init(data:)
initialiser with nil.

The reason why you can’t access the file is that

URL
does not expand the tilda character. In a real app that’s rarely a problem. In your test code, there are ways to expand the tilda but I recommend that you just pass in an absolute path.

Also, be aware that

XMLParser
is not intended to be subclassed. Rather, you are expected to handle parser results via the parser delegate. There’s a bunch of examples of this on the developer web site (I usually point folks at SeismicXML) but I don’t know of any in Swift.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks eskimo. I removed the tildar and fed a absolute path "file:///Users/yimin/Downloads/test1.xml" as URL, but the problem persists. I am very surprised about the fact that if I just initialize a XMLParser object, it works no matter tilda is there or not. I began to suspect this might be a bug in swfit 3.


I am from C++ background, and am new to swift. So please allow me to ask a very basic question: Music XML is a file format for musicians to exchange music scores. It is a specialized XML format with particular declaration. From OOP concept, it is obvious to me that the MusicXMLParser should be a subclass of XMLParser, just as class "Dog" should inherit "Animal'. Why XMLParser should not be inherited in this way? from OOP perspective.


Also, is it possible to step into the excution of the initalizers in XMLParser?

I removed the tildar and fed a absolute path "file:///Users/yimin/Downloads/test1.xml" as URL, but the problem persists.

Did you make this change literally? That is, write this:

let inputPath = "file:///Users/yimin/Downloads/test1.xml"
let inputURL = URL(fileURLWithPath: inputPath)

If so, that’s still not correct (sorry I didn’t point this out last time).

URL.init(fileURLWithPath:)
expects a path, so you should write this:
let inputPath = "/Users/yimin/Downloads/test1.xml"
let inputURL = URL(fileURLWithPath: inputPath)

You could have used

URL.init(string:)
but I recommend that you stick with
init(fileURLWithPath:)
because it takes care of some wacky edge cases.

As to why this traps, that seems to be the result of weird interactions between the Swift wrapper and the Foundation implementation. Given that

XMLParser.init(contentsOf:)
is a failable initialiser, I would expect it to respond to a bogus file URL by either:
  • returning nil, or

  • returning a valid parser that then fails when you run it (which is what Objective-C does)

I’ve filed a bug about this (r. 28691540).

The obvious workaround here is to not subclass

XMLParser
(see below). Alternatively, you could call
init(data:)
or
init(stream:)
.

Why XMLParser should not be inherited in this way?

Consider the following

XMLParser
delegate methods:
parser(_:didStartElement:namespaceURI:qualifiedName:attributes:)
parser(_:didEndElement:namespaceURI:qualifiedName:)

These are absolutely critical to how

XMLParser
works; essentially the parser calls these delegate methods as it enters and leaves the various XML elements.

Now, look at the public API for

XMLParser
itself. There’s no equivalent:
didStartElement(_:namespaceURI:qualifiedName:attributes:)
didEndElement(_:namespaceURI:qualifiedName:)

methods to override. So, how is your subclass going to function at all? Your only option is to make

MusicXMLParser
its own delegate. There’s nothing inherently wrong about that but it does limit your options. For example, the SeismicXML sample that I referenced in my earlier post puts its parsing code in an
Operation
subclass, and you can’t simultaneously be a subclass of both
XMLParser
and
Operation
.

Speaking more philosophically, inheritance is one of many different programming paradigms supported by Swift. When you use library code it’s best to follow the paradigm expected by that code. So, for example:

  • if you’re using

    Operation
    , in many cases you’ll find yourself subclassing
    Operation
    because that’s how that class is designed
  • if you’re using

    XMLParser
    , you almost never subclass
    XMLParser
    because it was designed with delegation in mind

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you for your detailed explaination. I learned a lot.