在 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 中覆盖具有不同类型的超类属性的主要内容,如果未能解决你的问题,请参考以下文章