【问题标题】:Do Swift inner classes have access to self of outer class?Swift 内部类可以访问外部类的 self 吗?
【发布时间】:2014-09-09 00:34:26
【问题描述】:

我来自 Java 背景,当您声明内部类时,它要么是静态的,并且无法访问外部类的实例,要么它不是静态的,并且可以访问外部类的实例正在运行的类。见http://en.wikipedia.org/wiki/Inner_class#Types_of_nested_classes_in_Java

Swift 有这方面的概念吗?根据我的测试,我似乎无法访问Outerself 对象,但我肯定做错了什么。

class Outer {
    let value = ""
    class Inner {
        func foo() {
            let bar = value // 'Outer.Type' does not have a member named 'value'
        }
    }
}

【问题讨论】:

    标签: swift


    【解决方案1】:

    AFAIK,您无法开箱即用地访问外部类。

    但你能做的是:

    class Outer {
    
        let value = ""
        var inner = Inner()
    
        class Inner {
    
            weak var parent: Outer! = nil
    
            func foo() {
                let bar = parent.value
            }
    
        }
    
        init() {
            inner.parent = self
        }
    
    }
    

    或者:

    class Outer {
    
        class Inner {
    
            unowned let parent: Outer
    
            init(parent: Outer) {
                self.parent = parent
            }
    
        }
    
        let value = ""
        var inner: Inner! = nil
    
        init() {
            inner = Inner(parent: self)
        }
    
    }
    

    【讨论】:

    • 您的解决方案将您限制为每个 Outer 的 Inner 类的单个实例。我不认为这一般是需要的,但在某些情况下会很好。如果你在 Inner 的构造函数中设置引用,你就可以解决这个问题。
    • 我已将此标记为解决方案,因为我认为这是我们现在可以使用 Swift 获得的最接近的解决方案(假设您在内部的 init 中设置了 ref)。但是,我认为看待这个问题的另一种方法是 Swift 不支持内部类访问外部类的范围。您的解决方案基本上是通过手动完成所有工作来实现这一点。这种方法让我想起了通过使用静态函数来创建实例化方法的尝试,这些静态函数将 this/self 的引用作为它们的第一个参数。
    • 你会不会将外部类(在 Inner 中)声明为无主 - 例如:unowned var parent: Outer
    • @inder:它根本不适用于无主。 Swift 返回编译错误。你有一个说明你的意思的sn-p吗?
    • @inder unowned 和 var 不能一起使用。如果您更改为无主​​,您还必须更改为使用 let。这迫使您为 Inner 使用构造函数(这很好,请参阅我的第一条评论)。代码如下所示: class Outer { let value = "" func baz() { var inner = Inner(parent: self) } class Inner { unowned let parent: Outer init(parent: Outer) { self.parent = parent } func foo() { let bar = parent.value } } }
    【解决方案2】:

    嵌套类型对包含它们的类型没有任何特殊访问权限。但我不认为这就是这里发生的一切。您似乎对类与实例有点模糊。

    value 是 Outer 类的属性。这意味着每个 Outer 实例都有自己的value。 Inner 是一个单独的类,存在于 Outer 的命名空间中。所以当你写let bar = value时,没有value这样的东西可以访问,因为它只存在于Outer的实例中,而我们手头没有任何实例。如果它是一个类属性,你可以使用let bar = Outer.value

    【讨论】:

    • 嗯,所以如果 Swift 使用 'class' 关键字来表示属性/函数的静态,那么当我们使用 'class' 关键字时,我们不得不将内部类声明为静态来定义它们。
    • @Sky:我不这么认为,因为该类不是属性或函数。它只是开始类定义的普通 class 关键字。
    • “你似乎对类和实例有点模糊。”我不这么认为,因为在 Java 中,您能够从内部类的实例访问外部类的实例变量value
    【解决方案3】:

    不,要在 Swift 中拥有类似的功能,您需要对其进行硬编码。

    class Outer {
        var value = 0
    
        init(_ value: Int) {
            self.value = value
        }
    
        func inner() -> Inner {
            return Inner(self)
        }
    
        class Inner {
            let outer: Outer
            var value = 1
    
            init(_ outer: Outer) {
                self.outer = outer
            }
    
            func doSomething() {
                print("Outer ", outer.value, " Inner ", value)
            }
        }
    }
    
    let i1 = Outer(1).inner()
    i1.doSomething() // Outer 1 Inner 1
    
    let i2 = Outer(2).inner()
    i2.doSomething() // Outer 2 Inner 1
    

    【讨论】:

      【解决方案4】:

      任何语言的内部类都是一个非常无用的结构。它们对关注点分离没有任何作用。它们仅提供包装类内部的可视化分组,但代码仍然高度纠缠于各处的交叉依赖关系。

      抽象的正确做法是隔离处理尽可能少的一组问题(理想情况下是一个)的部分。隔离意味着不可能有交叉依赖,这在推理代码时意义重大。

      问问你自己:你真正需要这个外部类的引用是为了什么? 唯一可能的答案是仅访问其 API 的某些特定部分。 事实证明,这就是将内部类专门化为特定外部类的原因。

      考虑以下示例:

      class CustomSectionContentController : UIViewController {
      
        @IBOutlet weak var webView: UIWebView!
      
        lazy var webViewDelegate: WebViewDelegate = WebViewDelegate()
      
        override func viewDidLoad() {
          super.viewDidLoad()
          webView.delegate = webViewDelegate
        }
      
        class WebViewDelegate: NSObject, UIWebViewDelegate {
      
          var overlay: ProgressOverlay?
      
          func webViewDidStartLoad(webView: UIWebView) {
            // Here I want to access the 'view' of the outer class:
            overlay = ProgressOverlay.cover(outerSelf.view)
          }
      
          func webViewDidFinishLoad(webView: UIWebView) {
            overlay?.remove()
          }
      
        }
      }
      

      当然,您可以使用其他答案中建议的多种方法中的任何一种来解决缺少的outerSelf 功能,但此示例实际上表明存在一段易于区分的代码,可以将其隔离。考虑以下替代方案:

      class CustomSectionContentController : UIViewController {
      
        @IBOutlet weak var webView: UIWebView!
      
        lazy var webViewDelegate: ProgressOverlayWebViewDelegate =
          ProgressOverlayWebViewDelegate(view: self.view)
      
        override func viewDidLoad() {
          super.viewDidLoad()
          webView.delegate = webViewDelegate
        }
      
      }
      
      class ProgressOverlayWebViewDelegate: NSObject, UIWebViewDelegate {
      
        let view: UIView
      
        init(view: UIView) {
          self.view = view
        }
      
        var overlay: ProgressOverlay?
      
        func webViewDidStartLoad(webView: UIWebView) {
          overlay = ProgressOverlay.cover(view)
        }
      
        func webViewDidFinishLoad(webView: UIWebView) {
          overlay?.remove()
        }
      
      }
      

      看到区别了吗?我们的控制器现在根本不处理 WebView 委托的问题区域。我们现在还有一个孤立的将军ProgressOverlayWebViewDelegate,它对使用它的控制器一无所知。这意味着我们可以在其他控制器中重用这个委托。

      一开始你可能不相信,但事实证明,上述方法适用于所有涉及内部类的场景。一般而言,关注点的隔离是抽象和编程良好实践的基石,因此每当您遇到此类问题时,请重新考虑。

      【讨论】:

      • 我认为您至少在内部类方面是错误的,因为它们存在于 Swift 中。 Swift 中的所有内部类都将它们命名为封闭类型。它们根本没有引用封闭类型的数据。例如,如果要创建一个链表,可能会将其设为 Node 一个内部类,这样它就不会弄乱命名空间。
      【解决方案5】:

      您可以像这样解决这个问题:

      class Outer {
          let value = ""
          class Inner {
              func foo(outer: Outer) {
                  let bar = outer.value
              }
          }
      }
      

      或者,您可以将其传递给Inner 的初始化程序,而不是将Outer 实例传递给foo 方法。但是,如果 Innerenum,则该技巧将不起作用,因为 Swift enum 不能具有属性(除了关联值,它们类似于属性)。

      此解决方法等效于 Java 为内部类隐式执行的操作,以避免繁琐的传递参数和显式引用的工作给您带来负担。

      IMO Java 内部类特性的基本原理类似于闭包的基本原理:该特性允许代码隐式使用环境中的变量(在这种情况下,环境是Outer 类实例)。我不知道为什么 Swift 缺少内部类。如果 Swift 被增强以执行内部类,则必须更改许多现有的 Swift 代码以将 static 放在类声明的前面。值得,IMO。

      【讨论】:

        【解决方案6】:

        好吧,您可以使用继承,以便将外部用作内部的超类。

        class Outer {
            let value = ""
            class Inner: Outer {
                func foo() {
                    let bar = value //value is now accessable as it has internal access from the superclass
                }
            }
        }
        

        【讨论】:

        • 那么为什么不从它开始呢? :)
        • 好吧,我从上面复制了代码,只有在我完成答案时才意识到,但是好的,我会改变它
        • 我认为这是我的错,我的问题没有解释为什么我们想要一个内部类。您的解决方案确实“解决”了我精简的代码示例,但它并没有真正让我们拥有我们想要的机制。内部类应该是外部类使用的工具。请参阅 google.com/?#q=java+inner+class+use 以获取有关我正在寻找的内容的更详细说明。
        • 此类Inner 类的实例无权访问Outer 类实例。
        【解决方案7】:

        如果您有很多内部类并且想要编写更少的代码行,那么您可以使用接受 Outer 实例的初始化程序创建基类并从中继承所有内部类:

        class Outer {
            func testInner() {
                print("test")
            }
        
            class InnerBase {
                let outer: Outer
        
                init(outer: Outer) {
                    self.outer = outer
                }
            }
        
            class Inner1: InnerBase {
                func test() {
                    outer.testInner()
                }
            }
        
            class Inner2: InnerBase {
                func test() {
                    outer.testInner()
                }
            }
        
            func test() {
                Inner1(outer: self).test()
                Inner2(outer: self).test()
            }
        }
        
        Outer().test()
        

        【讨论】:

          猜你喜欢
          • 2011-08-11
          • 2021-04-21
          • 1970-01-01
          • 2012-11-22
          • 2017-05-31
          • 2014-02-11
          • 1970-01-01
          • 2021-09-25
          相关资源
          最近更新 更多