【问题标题】:Overriding superclass property with different type in Swift在 Swift 中覆盖具有不同类型的超类属性
【发布时间】:2020-04-03 11:28:42
【问题描述】:

在 Swift 中,有人可以解释如何用从原始属性子类化的另一个对象覆盖超类上的属性吗?

举个简单的例子:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override let chassis = RacingChassis() //Error here
}

这给出了错误:

Cannot override with a stored property 'chassis'

如果我将机箱设置为“var”,则会收到错误消息:

Cannot override mutable property 'chassis' of type 'Chassis' with covariant type 'RacingChassis'

我可以在“覆盖属性”下的指南中找到的唯一内容表明我们必须覆盖 getter 和 setter,这可能适用于更改属性的值(如果它是 'var'),但是如何更改属性类?

【问题讨论】:

    标签: swift


    【解决方案1】:

    与其他答案略有不同,但更简单、更安全,还有一些不错的好处。

    class Chassis {}
    class RacingChassis : Chassis {}
    
    class Car {
        let chassis: Chassis
    
        init(chassis: Chassis) {
            self.chassis = chassis
        }
    }
    class RaceCar: Car {
        var racingChassis: RacingChassis? {
            get {
                return chassis as? RacingChassis
            }
        }
    }
    

    好处包括对必须是什么底盘没有限制(var、let、可选等),并且很容易继承 RaceCar。 RaceCar 的子类可以有自己的底盘(或赛车底盘)计算值。

    【讨论】:

    • 这个答案的问题是赛车有 2 个底盘! raceCar.chassis 和 raceCar.racingChassis
    • 我已经修改了我的答案,通过提供一个初始化程序来指定底盘,使我的答案更加现实,以便可以使用 RacingChassis 初始化汽车。希望更清楚一点,实际上只有一个底盘,但它可以被引用为 raceCar.chassis 或 raceCar.raceChassis(后者在你有一辆赛车并且你知道你想要 RacingChassis 的场景中)。显然存在一个漏洞,您可以在没有 RacingChassis 的情况下拥有 RaceCar,但至少可以通过可选组件优雅地处理它。
    【解决方案2】:

    使用泛型简单,例如

    class Chassis {
        required init() {}
    }
    class RacingChassis : Chassis {}
    
    class Car<ChassisType : Chassis> {
        var chassis = ChassisType()
    }
    
    
    let car = Car()
    let racingCar = Car<RacingChassis>()
        
    let c1 = car.chassis
    let c2 = racingCar.chassis
        
    print(c1) // Chassis
    print(c2) // RacingChassis
    

    另外,Chassis 甚至不需要是子类:

    protocol Chassis {
        init()
    }
    class CarChassis: Chassis{
        required init() {
        }
    }
    class RacingChassis : Chassis {
        required init() {
        }
    }
    
    class Car<ChassisType : Chassis> {
        var chassis = ChassisType()
    }
    

    【讨论】:

      【解决方案3】:

      Swift 不允许您更改任何变量或属性的类类型。相反,您可以在处理新类类型的子类中创建一个额外的变量:

      class Chassis {}
      class RacingChassis : Chassis {}
      
      class Car {
          var chassis = Chassis()
      }
      class RaceCar: Car {
          var racingChassis = RacingChassis()
          override var chassis: Chassis {
              get {
                  return racingChassis
              }
              set {
                  if let newRacingChassis = newValue as? RacingChassis {
                      racingChassis = newRacingChassis
                  } else {
                      println("incorrect chassis type for racecar")
                  }
              }
          }
      }
      

      似乎不能使用 let 语法声明属性并在其子类中使用 var 覆盖它,反之亦然,这可能是因为超类实现可能不期望该属性在初始化后发生更改。所以在这种情况下,需要在超类中使用“var”声明属性以匹配子类(如上面的 sn-p 所示)。如果无法更改超类中的源代码,那么最好在每次底盘需要变异时销毁当前的 RaceCar 并创建一个新的 RaceCar。

      【讨论】:

      • 抱歉刚刚删除了我的评论,然后才看到你的回复。我遇到了一个问题,因为在我的真实情况下,我的超类是一个带有 strong 属性的 Objective-C 类,我在尝试覆盖它时遇到了错误 - 但似乎我错过了它转换为“隐式展开的可选” (chassis!) 在 Swift 中,所以 override var chassis : Chassis! 修复它。
      • 这不再适用于 Xcode 6.1 下的 Swift 1.1。生成错误:“不能用 'var' 的 getter 覆盖不可变的 'let' 属性 'chassis'”。有更好的解决方案的想法吗?
      • 也不再适用于 Xcode 6.3 beta 下的 Swift 1.2。无法使用协变类型“Type2”覆盖“Type1”类型的可变属性“A”
      • 这真的很蹩脚,也是 Swift 的失败,imo。由于 RacingChassis 一个底盘,编译器应该没有问题允许您在子类中细化属性的类。许多语言都允许这样做,而不支持它会导致像这样丑陋的解决方法。没有冒犯的意思。 ://
      • 可选选项提供了一种暗示变量可能不存在的方法,因为它是由不再返回预期值的函数提供的。如果函数被覆盖,则可能会发生这种情况。详情请看我的回答。
      【解决方案4】:

      根据您计划如何使用该属性,最简单的方法是为您的子类使用可选类型,并为超类覆盖 didSet {} 方法:

      class Chassis { }
      class RacingChassis: Chassis { }
      
      class Car {
          // Declare this an optional type, and do your 
          // due diligence to check that it's initialized
          // where applicable
          var chassis: Chassis?
      }
      class RaceCar: Car {
          // The subclass is naturally an optional too
          var racingChassis: RacingChassis?
          override var chassis: Chassis {
              didSet {
                  // using an optional, we try to set the type
                  racingChassis = chassis as? RacingChassis
              }
          }
      }
      

      显然,您需要花一些时间检查以确保可以以这种方式初始化类,但是通过将属性设置为可选,您可以保护自己免受强制转换不再起作用的情况。

      【讨论】:

        【解决方案5】:

        以下允许在基类和派生类中使用单个对象。在派生类中,使用派生对象属性。

        class Car {
            var chassis:Chassis?
        
            func inspect() {
                chassis?.checkForRust()
            }
        }
        
        class RaceCar: Car {
            var racingChassis: RacingChassis? {
                get {
                    return chassis as? RacingChassis
                } 
            }
        
            override func inspect() {
                super.inspect()
                racingChassis?.tuneSuspension()
            }
        }
        

        【讨论】:

          【解决方案6】:
          class Chassis {}
          class RacingChassis : Chassis {}
          
          class Car {
              fileprivate let theChassis: Chassis
              var chassis: Chassis {
                  get {
                      return theChassis
                  }
              }
              fileprivate init(_ chassis: Chassis) {
                  theChassis = chassis
              }
              convenience init() {
                  self.init(Chassis())
              }
          }
          class RaceCar: Car {
              override var chassis: RacingChassis {
                  get {
                      return theChassis as! RacingChassis
                  }
              }
              init() {
                  super.init(RacingChassis())
              }
          }
          

          【讨论】:

            【解决方案7】:

            您可以使用泛型来实现它:

            class Descriptor {
                let var1 = "a"
            }
            
            class OtherDescriptor: Descriptor {
                let var2 = "b"
            }
            
            class Asset<D: Descriptor> {
                let descriptor: D
            
                init(withDescriptor descriptor: D) {
                    self.descriptor = descriptor
                }
            
                func printInfo() {
                    print(descriptor.var1)
                }
            }
            
            class OtherAsset<D: OtherDescriptor>: Asset<D> {
                override func printInfo() {
                    print(descriptor.var1, descriptor.var2)
                }
            }
            
            let asset = Asset(withDescriptor: Descriptor())
            asset.printInfo() // a
            
            let otherAsset = OtherAsset(withDescriptor: OtherDescriptor())
            otherAsset.printInfo() // a b
            

            使用这种方法,您将获得 100% 类型安全的代码,而无需强制解包。

            但这是一种 hack,如果您需要重新定义多个属性,那么您的类声明将看起来一团糟。所以要小心这种方法。

            【讨论】:

              【解决方案8】:

              只需使用不同的命名约定设置新的 imageview 属性,例如 imgview,因为 imageView 已经是它自己的属性,我们不能分配 2 个强属性。

              【讨论】:

                【解决方案9】:

                试试这个:

                class Chassis{
                     var chassis{
                         return "chassis"
                     } 
                }
                
                class RacingChassis:Chassis{
                     var racing{
                         return "racing"
                     } 
                }
                
                class Car<Type:Chassis> {
                     let chassis: Type
                     init(chassis:Type){
                        self.chassis = chassis
                     }
                }
                
                class RaceCar: Car<RacingChassis> {
                     var description{
                         return self.chassis.racing
                     } 
                }
                

                然后:

                let racingChassis = RacingChassis()
                let raceCar = RaceCar(chassis:racingChassis)
                print(raceCar.description) //output:racing
                

                详情见http://www.mylonly.com/14957025459875.html

                【讨论】:

                • 干净整洁啊
                【解决方案10】:

                试试这个:

                class Chassis {}
                class RacingChassis : Chassis {}
                class SuperChassis : RacingChassis {}
                
                class Car {
                    private var chassis: Chassis? = nil
                    func getChassis() -> Chassis? {
                        return chassis
                    }
                
                    func setChassis(chassis: Chassis) {
                        self.chassis = chassis
                    }
                }
                
                class RaceCar: Car {
                    private var chassis: RacingChassis {
                        get {
                            return getChassis() as! RacingChassis
                        }
                        set {
                            setChassis(chassis: newValue)
                        }
                    }
                
                    override init() {
                        super.init()
                
                        chassis = RacingChassis()
                    }
                }
                
                class SuperCar: RaceCar {
                    private var chassis: SuperChassis {
                        get {
                            return getChassis() as! SuperChassis
                        }
                        set {
                            setChassis(chassis: newValue)
                        }
                    }
                
                    override init() {
                        super.init()
                
                        chassis = SuperChassis()
                    }
                }
                

                【讨论】:

                  【解决方案11】:

                  我已经看到了为什么使用变量而不是函数来设计 API 是有问题的,并且对我来说使用计算属性感觉像是一种解决方法。有充分的理由将您的实例变量封装起来。在这里,我创建了 Car 符合的汽车协议。该协议有一个返回 Chassis 对象的访问器方法。由于 Car 符合它, RaceCar 子类可以覆盖它并返回不同的 Chassis 子类。这允许 Car 类对接口 (Automobile) 进行编程,并且了解 RacingChassis 的 RaceCar 类可以直接访问 _racingChassis 变量。

                  class Chassis {}
                  class RacingChassis: Chassis {}
                  
                  protocol Automobile {
                      func chassis() -> Chassis
                  }
                  
                  class Car: Automobile {
                      private var _chassis: Chassis
                  
                      init () {
                          _chassis = Chassis()
                      }
                  
                      func chassis() -> Chassis {
                          return _chassis
                      }
                  }
                  
                  class RaceCar: Car {
                      private var _racingChassis: RacingChassis
                  
                      override init () {
                          _racingChassis = RacingChassis()
                          super.init()
                      }
                  
                      override func chassis() -> Chassis {
                          return _racingChassis
                      }
                  }
                  

                  使用变量设计 API 的另一个例子是当您在协议中有变量时。如果您想将所有协议功能分解为扩展,则可以,除了存储的属性不能放在扩展中并且必须在类中定义(要编译它,您必须取消注释代码AdaptableViewController 类并从扩展中移除模式变量):

                  protocol Adaptable {
                      var mode: Int { get set }
                      func adapt()
                  }
                  
                  class AdaptableViewController: UIViewController {
                      // var mode = 0
                  }
                  
                  extension AdaptableViewController: Adaptable {
                  
                      var mode = 0 // compiler error
                  
                      func adapt() {
                          //TODO: add adapt code
                      }
                  }
                  

                  上面的代码会有这个编译器错误:“扩展可能没有存储的属性”。以下是如何重写上面的示例,以便可以通过使用函数将协议中的所有内容在扩展中分离出来:

                  protocol Adaptable {
                      func mode() -> Int
                      func adapt()
                  }
                  
                  class AdaptableViewController: UIViewController {
                  }
                  
                  extension AdaptableViewController: Adaptable {
                      func mode() -> Int {
                          return 0
                      }
                      func adapt() {
                          // adapt code
                      }
                  }
                  

                  【讨论】:

                    【解决方案12】:

                    Dash 提供的解决方案运行良好,只是必须使用 let 关键字而不是 var 声明超类。这是一个可行但不推荐的解决方案!

                    下面的解决方案将使用 Xcode 6.2、SWIFT 1.1 编译(如果所有类都在不同的 swift 文件中),但应该避免,因为它可能导致意外行为(包括崩溃,尤其是在使用非可选类型时)。 注意:这不适用于 XCODE 6.3 BETA 3、SWIFT 1.2

                    class Chassis {}
                    class RacingChassis : Chassis {}
                    class Car {
                        var chassis:Chassis? = Chassis()
                    }
                    
                    class RaceCar: Car {
                        override var chassis: RacingChassis? {
                            get {
                                return super.chassis as? RacingChassis
                            }
                            set {
                                super.chassis = newValue
                            }
                        }
                    }
                    

                    【讨论】:

                    • 它不适用于 Swift 2.0。它给出了“不能覆盖'机箱'类型的可变属性'机箱'?”具有协变类型 'RacingChassis?'"
                    【解决方案13】:

                    理论上,你可以这样做......

                    class ViewController {
                    
                        var view: UIView! { return _view }
                    
                        private var _view: UIView!
                    }
                    
                    class ScrollView : UIView {}
                    
                    class ScrollViewController : ViewController {
                    
                        override var view: ScrollView! { return super.view as ScrollView! }
                    }
                    
                    class HomeView : ScrollView {}
                    
                    class HomeViewController : ScrollViewController {
                    
                        override var view: HomeView! { return super.view as HomeView! }
                    }
                    

                    这在 Xcode 游乐场中完美运行。

                    但是,如果您在实际项目中尝试这样做,编译器错误会告诉您:

                    声明“视图”不能覆盖多个超类声明

                    到目前为止,我只检查了 Xcode 6.0 GM。

                    很遗憾,您必须等到 Apple 解决此问题。

                    我也提交了错误报告。 18518795

                    【讨论】:

                    • 这是一个有趣的解决方案,它适用于 6.1。
                    【解决方案14】:

                    这似乎有效

                    class Chassis {
                        func description() -> String {
                            return "Chassis"
                        }
                    }
                    class RacingChassis : Chassis {
                        override func description() -> String {
                            return "Racing Chassis"
                        }
                    
                        func racingChassisMethod() -> String {
                            return "Wrooom"
                        }
                    }
                    
                    class Car {
                        let chassis = Chassis()
                    }
                    class RaceCar: Car {
                        override var chassis: RacingChassis {
                        get {
                            return self.chassis
                        }
                        set {
                            self.chassis = newValue
                        }
                        }
                    }
                    
                    var car = Car()
                    car.chassis.description()
                    
                    var raceCar = RaceCar()
                    raceCar.chassis.description()
                    raceCar.chassis.racingChassisMethod()
                    

                    【讨论】:

                    • 这是因为机箱属性在Car 类中定义为let,因此无法更改。我们只能更改RaceCar 类的chassis 属性。
                    • 这不适用于 Swift 2.0。它给出了“错误:不能用'var'的getter覆盖不可变的'let'属性'chassis'”
                    • 这在 swift 4.0 中也不起作用。地面执行失败:错误:MyPlaygrounds.playground:14:15:错误:无法使用“var”覆盖var机箱的getter覆盖不可变的“let”属性“chassis”:RacingChassis { ^ MyPlaygrounds.playground:11:6:注意: 尝试在这里覆盖属性 let chassis = Chassis() ^
                    【解决方案15】:

                    您可以简单地创建另一个 RacingChassis 变量。

                    class Chassis {}
                    class RacingChassis : Chassis {}
                    class Car {
                        let chassis: Chassis
                        init(){
                            chassis = Chassis()
                    }}
                    
                    class RaceCar: Car {
                    let raceChassis: RacingChassis
                    init(){
                            raceChassis = RacingChassis()
                    }}
                    

                    【讨论】:

                    • 这行得通,但我认为 Dash 的回答更进一步,通过覆盖 chassis 并允许我们使用 chassis 属性获取/设置我们的 RacingChassis 实例。
                    猜你喜欢
                    • 2019-09-29
                    • 2014-10-19
                    • 2018-08-31
                    • 1970-01-01
                    • 1970-01-01
                    • 2019-06-12
                    • 1970-01-01
                    • 2018-07-26
                    相关资源
                    最近更新 更多