4 Replies
      Latest reply: Oct 13, 2016 9:53 AM by yiminpang RSS
      yiminpang Level 1 Level 1 (0 points)

        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?

        • Re: XMLParser in Swift 3
          eskimo Apple Staff Apple Staff (6,280 points)

          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"

            • Re: XMLParser in Swift 3
              yiminpang Level 1 Level 1 (0 points)

              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?

                • Re: XMLParser in Swift 3
                  eskimo Apple Staff Apple Staff (6,280 points)

                  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"