继承丑陋:Swift 子类改变了超类对自己存储属性的看法?



我有一个子类,它的继承链分解如下:

InputAccessoryEnabledTextField : UITextField : UIControl : UIView : UIResponder

InputAccessoryEnabledTextField提供覆盖:

private var myInputAccessoryController: UIInputViewController?
override var inputAccessoryViewController: UIInputViewController? {
get { myInputAccessoryController }
set { myInputAccessoryController = newValue }
}

上面的代码是我所寻求的解决方案,它是我刚刚在S.O.上问的一个问题的公认答案(@Sweeper)。它覆盖了UIResponder的一个实例属性。

然而,这对我来说没有意义。是如何工作的?

我的子类的超类UITextField怎么可能接受我的子类别(InputAccessoryEnabledTextField)提供的覆盖?

这难道不违反继承层次结构吗?难道不应该只有InputAccessoryEnabledTextFieldsub类才能看到它的覆盖,而不是超类吗?

或者重写是否应用于整个对象,这样每个继承的超类都可以看到某个任意最外层子类的状态?或者,是iOS文本子系统在做一些非常复杂的事情吗?

也许这对S.O.来说是一个过于抽象的问题,我不介意关闭或删除它,只是发布这个来避免机器人抱怨的评论中出现"对话"。

  • 注意:我在Swift 5文档的继承章节中没有发现太多关于它的清晰信息*

简而言之

这确实是属性的压倒一切。Swift通过生成相当于通过getter和setter访问属性的代码来处理属性。这允许通过重写getter和setter来重写属性。

更多解释

您的代码段覆盖了一个属性

在代码中,InputAccessoryEnabledTextField间接继承自具有现有属性inputAccessoryViewControllerUIResponder

您的代码片段定义了一个新的私有属性myInputAccessoryController,并将其用于重写继承的属性inputAccessoryViewController,更确切地说,用于重写其getter和setter。

就您的代码片段而言,目的

事实上,inputAccessoryViewController:的文档中甚至解释了这种覆盖的目的

此只读属性的值为零。

但是一个仅为实数且仅返回零的属性有什么用?

如果要将自定义控件附加到系统提供的输入视图控制器(如系统键盘)或自定义输入视图(…),请在UIResponder子类中将此属性重新声明为读写。

属性重写如何工作

虽然属性覆盖乍一看可能很奇怪,但一旦我们了解了以下内容,我们就会意识到这只是正常的覆盖机制:

子类不知道继承属性的存储或计算性质——它只知道继承属性具有特定的名称和类型。您必须始终声明要重写的属性的名称和类型,以使编译器能够检查您的重写是否与具有相同名称和类型的超类属性匹配。

在这里我们看到了Swift属性的威力。您可以将任何属性公开,并且仍然可以从封装和专业化中获益,像函数一样覆盖它。解释是一个属性有两个面:

  • 类内部实现细节:属性是存储的还是计算的
  • 外部世界的隐式类接口,包括子类:外部世界使用getter和setter。这些可以被覆盖
  • 同样的原理适用于didSet等属性观察器:即使基类没有为它们定义任何特殊行为,也可以覆盖它们

这里有一个无关但极端的玩具小例子来说明这个功能(我不推荐它的设计;-)):

class Lense {
init (opticalZoom: Int) {
magnifyingFactor = opticalZoom
}
// stored property
// And has implicitly a getter and a setter 
var magnifyingFactor : Int = 2
}
class ElectronicLense : Lense {
// overriden property
// it overrides getter and setter, and uses the propery of the super-class to store the value
override var magnifyingFactor: Int {
get { super.magnifyingFactor * 5 }
set { super.magnifyingFactor = newValue / 5 }
}
// pass through property
var opticalFactor : Int {
get {super.magnifyingFactor}
}
}

var le = ElectronicLense(opticalZoom: 3)
print ("Zoom: (le.magnifyingFactor) with an optical factor (le.opticalFactor)")

下面的示例代码演示了Swift超类通过最外层子类覆盖来体验自己的属性

(下面的例子证明@RobNapier是正确的,我最初通过成功覆盖UIResponder.inputAccessoryViewController并观察我的viewController在我的子类UITextView : UIResponder弹出键盘时激活来确认这一点)

优点:

@RobNaipier在评论中解释的Swift覆盖,至少从某些角度来看是有意义的。并且显然可以利用其有趣的灵活性

缺点:

然而,这并不是我所假设的,我对继承以这种方式工作感到有些震惊,因为凭直觉我意识到,让子类篡改超类对自己的看法是有潜在风险的,尤其是在不知道超类实现细节的情况下(就像UIKit专有实现代码的情况一样——苹果没有向公众发布源代码)

丑陋:

因此,虽然Swift继承可以让继承者实现有趣或有用的调整,并且在某些情况下非常方便,在实际使用中,例如使用UIKit,但它确实会导致预期的问题和混乱

Rob指出的成功之处在于,由于预期的不利因素,苹果越来越不鼓励使用UIKit进行类继承,SwiftUI也采用了struct+协议。

class TheSuperclass {
var x = 5
init() {
print("How superclass sees it before subclass  initialized: x = (x)")
}
func howSuperclassSeesItselfAfterSubclassInit() {
print("How superclass sees it after subclass   initialized: x = (x)")
}
}
class TheSubclass : TheSuperclass {
override var x : Int {
get { super.x + 10 }
set { super.x = newValue }
}
override init() {
super.init()
print("How subclass   sees it after superclass" + 
"initialized: x = (x), super.x = (super.x)")
}
}
TheSubclass().howSuperclassSeesItselfAfterSubclassInit()

当在操场上运行时,上面的代码显示以下内容:

在初始化子类之前,超类如何看待它:x=5超类初始化后,子类如何看待它:x=15,super.x=5初始化子类后,超类如何看待它:x=15

相关内容

  • 没有找到相关文章

最新更新