如果实现类被标记为私有,则不调用委托方法?

Posted

技术标签:

【中文标题】如果实现类被标记为私有,则不调用委托方法?【英文标题】:Delegate methods not called if implementing class is marked private? 【发布时间】:2015-04-13 18:13:52 【问题描述】:

这是一个运行良好的 Swift 代码的简短 sn-p 代码(其中“fine”被定义为“Parsing!”被打印一大堆以响应调用类方法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!")
      
    
  

当我尝试使用 Swift 的可见性功能时,问题就来了。特别是,我不想让 Parser 类对其他文件可见(因为没有理由让它可见)。但是,如果我通过private class Parser … 声明它,代码将停止工作! parser:didStartElement:namespaceURI:qualifiedName:attributes: 不再被调用!

这一切对我来说似乎很奇怪,不像它在任何其他语言中的工作方式。因此,我觉得以下两件事之一一定是真的:

    Swift 的命名空间系统充其量是很奇怪的。更清楚地说,它在我看来只是破碎。

    Swift 很好,我只是在做一些非常愚蠢的事情!如果是这样的话,那就太好了!请告诉我它是什么!

谢谢大家的帮助!


编辑:这是一个略微精简的版本。和以前一样,代码可以正常工作,直到 Parser 类被标记为 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!")
  

【问题讨论】:

无法复制。我已将您的代码复制到一个测试项目中,将 Parser 和 XMLParserDelegate 都标记为私有,它仍然可以正常工作。 @MartinR 奇怪。你和我一样在使用 Swift 1.2 吗? 不,那是 Swift 1.1。但是在 Swift 1.2 中,我可以通过将 Parser 或 XMLParserDelegate 或 didStartElement 方法标记为私有方法来重现它。 @MartinR 太棒了!这似乎是当时 Swift 1.2 中引入的一个错误。谢谢!我会密切关注这个问题,看看是否有其他人提出相关问题,然后将其打包为 Apple 的错误报告。 可以使用 MFMailComposeViewControllerDelegate 在 Swift 1.2 Xcode 6.3 beta 3 (6D543q) 上确认此行为 【参考方案1】:

在 XCode 版本 6.1 (6A1052c) 之前已经存在类似的错误,它已在该版本中修复,现在在 XCode 版本 6.3 (6D532l) 中再次引入。

当时(6.1 之前)还有另一个错误,您必须手动更改func parser(parser: NSXMLParser, didStartElement elementName: String... 方法的NSXMLParserDelegate 签名,方法是隐式解包一些参数以修复Thread 1: EXC_BAD_ACCESS (code=1, address=0x0) 错误。 阅读更多关于here

完成此操作后,如果使用它的类被标记为私有,则不会调用此方法,因此它必须至少是内部的才能工作。

Apple 在 6.1 中修复了此方法签名,然后我能够将解析器类设为私有 (see this),这在 XCode 6.3 / Swift 1.2 之前效果很好。

现在,我不确定这是一个错误还是预期的行为,所以我想我会等待下一个正式版本,看看我应该如何处理我的 XML Parsing library。

顺便说一句,Xcode 6.3 Beta 4 没有任何变化。

【讨论】:

【参考方案2】:

根据the investigation done by MartinR,这似乎是 Swift 1.2 中引入的一个错误(或类似的东西)。大概这种行为不是故意的,因为我不知道任何其他(合理的,静态类型的)语言将类标记为私有可以在运行时默默地破坏您的代码。我会跟进苹果,看看他们对这一切的看法!

【讨论】:

【参考方案3】:

这不足为奇。 NSXMLParserDelegate 包括以下内容:

optional func parser(_ parser: NSXMLParser, didStartElement elementName: String, namespaceURI namespaceURI: String, qualifiedName qualifiedName: String, attributes attributeDict: [NSObject : AnyObject])

因为它是可选的,所以NSXMLParser 中一定有一个doesRespondToSelector() 调用。如果底层类是私有的,函数会失败也就不足为奇了。 (鉴于它与动态 ObjC 调用的交互,如果它有效,也不会令人震惊;但是这两种方法都不应该被认为是错误的,并且您描述的更好地符合您的要求;也就是说这些方法是私有的。)

这里的正确答案是XMLParserDelegate 需要与NSXMLParserDelegate 的实现一起公开。 Parser 不必公开,任何非协议方法也不必公开。但是NSXMLParser 需要能够看到它的委托方法,如果您希望它调用它们。

更令人惊讶的是,这不是编译器错误。


编辑:虽然这并没有造成编译器错误(我觉得 可能是一个错误)让我感到惊讶,但的关键发现是 @ 987654330@ 表示私有。这意味着其他文件看不到该方法,因此respondsToSelector() 将失败。以更简单的形式证明这一点:

main.swift

import Foundation
private class Impl : NSObject, P 
    func run() 
        println("Running")
    

let c = Container(p: Impl())
c.go()

更多.swift

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
        
    

要详细了解为什么这是真的,我们可以查看p.dynamicType。如果p 是内部的,那么我们看到它的类型是Impl。如果它是私有的,我们看到它的类型是(Impl in _9F9099C659B8A128A78BAA9A7C0E0368)。使事物 private 使其类型和内部结构私有。

私人只是隐藏了比内部更多的东西。它影响运行时,而不仅仅是编译时间。

我想得越多,我就越明白为什么它不能给我们一个编译器错误。使用私有类实现内部协议是合法的。当我们将它作为参数传递给另一个访问区域,然后尝试动态地自省它时,它会横向移动。答案是“不要那样做”。

未来可能会发生变化。见https://devforums.apple.com/message/1073092#1073092。值得提出一个错误报告,但我仍然不会认为这是一个错误;这当然可以是预期的行为。

【讨论】:

这个解释对我来说不太合理。问题是,如果我将 XMLParserDelegate 保留为默认的内部可见性,它就可以正常工作。由于NSXMLParser 是在我的模块之外定义的,因此在这种情况下internalprivate 之间应该没有区别。我错过了什么吗? 这仍然没有让我感到震惊。如果 internal 不适用于代表,大多数代码都会中断。但我通常会发现,如果我将协议一致性标记为私有,Swift 会给我一个错误。我怀疑您的嵌套妨碍了错误(可能是由于编译器中的错误,但请注意 Martin 没有看到此问题)。 虽然如果internal 不起作用,大多数代码肯定会崩溃,但这并不能解释为什么private 不起作用是合理的。这对我来说仍然是一个错误。至于嵌套,我提供了一个未嵌套版本的代码,在原帖中仍然存在同样的问题。 私有类现在可以实现任何非 ObjC 协议,以及任何没有可选方法的 ObjC 协议。唯一的冲突是可选的一致性(在 Swift 协议中不可用)。我不熟悉 Java 和 C# 如何管理私有类协议中的可选方法。 啊,你又说对了!不幸的是,大多数 Objective-C 协议确实有可选方法,因此它几乎好像我无法实现我想要实现的任何协议——至少不能使用私有类。

以上是关于如果实现类被标记为私有,则不调用委托方法?的主要内容,如果未能解决你的问题,请参考以下文章

JAVA框架如何实现调用接口的实现类的呢?例实现httpsessionlistener接口类被调。

Google Cloud:将 OAuth 密钥标记为私有

如果不多次调用它们,您是不是应该将代码重构为私有方法? [关闭]

面对对象之私有属性,以及单双下划线 | Pythoon

AVAudioRecorder 中断委托方法没有被调用

java中私有的属性、静态成员可以被子类继承吗?