在 Swift 中覆盖具有不同类型的超类属性

Posted

技术标签:

【中文标题】在 Swift 中覆盖具有不同类型的超类属性【英文标题】:Overriding superclass property with different type in Swift 【发布时间】:2014-07-28 10:50:14 【问题描述】:

在 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'),但是如何更改属性类?

【问题讨论】:

【参考方案1】:

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 一个底盘,编译器应该没有问题允许您在子类中细化属性的类。许多语言都允许这样做,而不支持它会导致像这样丑陋的解决方法。没有冒犯的意思。 :// 可选选项提供了一种暗示变量可能不存在的方法,因为它是由不再返回预期值的函数提供的。如果函数被覆盖,则可能会发生这种情况。详情请看我的回答。【参考方案2】:

这似乎有效

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() ^【参考方案3】:

试试这个:

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

【讨论】:

干净整洁啊【参考方案4】:

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

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。【参考方案5】:

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?'"【参考方案6】:

我已经看到了为什么使用变量而不是函数来设计 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
    

【讨论】:

【参考方案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】:

根据您计划如何使用该属性,最简单的方法是为您的子类使用可选类型,并为超类覆盖 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
        
    

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

【讨论】:

【参考方案9】:

您可以简单地创建另一个 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 实例。【参考方案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】:
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())
    

【讨论】:

【参考方案12】:

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

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()
    

【讨论】:

【参考方案13】:

简单的使用泛型,例如

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()

【讨论】:

【参考方案14】:

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

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,但至少可以通过可选组件优雅地处理它。【参考方案15】:

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

【讨论】:

以上是关于在 Swift 中覆盖具有不同类型的超类属性的主要内容,如果未能解决你的问题,请参考以下文章

子类是不是从 Swift 中的超类扩展继承便利初始化程序?

面向对象----属性,约束,super深入了解

swift 学习- 14 -- 继承

尝试快速初始化超类属性但遇到错误

尝试使用前向类“NSManagedObjectModel”作为 Swift 类模型的超类

使用不同的兼容类型覆盖属性