Here's a short snippet of Swift code that works fine (where "fine" is defined as "Parsing!" being printed a whole bunch in response to calling the class method Parse.parse
):
import Foundation
class Parse {
class func parse(stream: NSInputStream) {
return Parser().parse(stream)
}
class Parser: NSObject, NSXMLParserDelegate {
func parse(stream: NSInputStream) {
let XMLParser = NSXMLParser(stream: stream)
let delegate = XMLParserDelegate()
XMLParser.delegate = delegate
XMLParser.parse()
}
class XMLParserDelegate: NSObject, NSXMLParserDelegate {
func parser(
parser: NSXMLParser,
didStartElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [NSObject : AnyObject])
{
NSLog("Parsing!")
}
}
}
}
The problem comes when I attempt to use Swift's visibility features. In particular, I do not want to make the Parser
class visible to other files (because there is no reason for it to be visible). If I declare it via private class Parser …
however, the code ceases to work! parser:didStartElement:namespaceURI:qualifiedName:attributes:
is no longer called!
This all seems bizarre to me and unlike how it would work in any other language. As such, I feel like one of the following two things must be true:
Swift's system for namespacing is, at best, weird. More plainly, it just seems broken to me.
Swift is fine and I am just doing something very silly! If that's the case, then great! Please let me know what it is!
Thanks for the help, everyone!
Edit: Here's a slightly trimmed-down version. As before, the code works fine until the Parser
class is marked private
:
import Foundation
class Parse {
class func parse(stream: NSInputStream) {
return Parser().parse(stream)
}
}
class Parser: NSObject, NSXMLParserDelegate {
func parse(stream: NSInputStream) {
let XMLParser = NSXMLParser(stream: stream)
XMLParser.delegate = self
XMLParser.parse()
}
func parser(
parser: NSXMLParser,
didStartElement elementName: String,
namespaceURI: String?,
qualifiedName qName: String?,
attributes attributeDict: [NSObject : AnyObject])
{
NSLog("Parsing!")
}
}
This shouldn't be surprising. NSXMLParserDelegate
includes the following:
optional func parser(_ parser: NSXMLParser, didStartElement elementName: String, namespaceURI namespaceURI: String, qualifiedName qualifiedName: String, attributes attributeDict: [NSObject : AnyObject])
Since it's optional, there must somewhere in NSXMLParser
be a doesRespondToSelector()
call. It shouldn't be surprising that function would fail if the underlying class is private. (Given its interaction with dynamic ObjC calls, it wouldn't be shocking if it worked, either; but neither approach should be considered broken, and what you describe better matches what you asked for; which is that these methods be private.)
The right answer here is that XMLParserDelegate
needs to be public along with its implementation of NSXMLParserDelegate
. Parser
doesn't have to be public, and any non-protocol methods don't need to be public. But NSXMLParser
needs to be able to see its delegate methods if you want it to call them.
What's a bit more surprising is that this isn't a compiler error.
EDIT: While it still surprises me that this doesn't create a compiler error (and I feel that is probably a bug), the key finding is that private
means private. It means that other files cannot see the method, and so respondsToSelector()
will fail. To demonstrate this in a simpler form:
import Foundation
private class Impl : NSObject, P {
func run() {
println("Running")
}
}
let c = Container(p: Impl())
c.go()
import Foundation
@objc internal protocol P: NSObjectProtocol {
optional func run()
}
// Change internal to private to see change
internal struct Container {
let p: P
func go() {
println(p.dynamicType) // Impl for internal, (Impl in ...) for private
if p.respondsToSelector(Selector("run")) {
p.run!() // if run is internal or public
} else {
println("Didn't implement") // if run is private
}
// Or the Swiftier way:
if let run = p.run {
run() // if run is internal or public
} else {
println("Didn't implement") // if run is private
}
}
}
To see a little more of the details on why this is true, we can look at p.dynamicType
. If p
is internal, then we see that its type is Impl
. If it is private, we see its type is (Impl in _9F9099C659B8A128A78BAA9A7C0E0368)
. Making things private
makes their type and internal structure private.
Private just hides much more than internal. It impacts the runtime, not just the compile time.
And the more I think about it, the more I see why it can't give us a compiler error. It is legal to implement an internal protocol with a private class. The place it goes sideways is when we pass it as a parameter to another access area and then try to introspect it dynamically. And the answer is "don't do that."
It is possible that will change in the future. See https://devforums.apple.com/message/1073092#1073092. It's worth bringing up as a bugreport, but I still wouldn't assume it's a bug; it could certainly be intended behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With