Переопределение свойства суперкласса другим типом в Swift

129

Может ли кто-нибудь объяснить в 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'

Единственное, что я смог найти в руководстве в разделе «Переопределение свойств», указывает на то, что мы должны переопределить методы получения и установки, которые могут работать для изменения значения свойства (если это 'var'), но как насчет изменения класса свойства ?

Джеймс
источник

Ответы:

115

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' в суперклассе, чтобы соответствовать подклассу (как показано во фрагменте выше). Если нельзя изменить исходный код в суперклассе, то, вероятно, лучше всего уничтожить текущую RaceCar и создавать новую RaceCar каждый раз, когда требуется мутировать шасси.

тир
источник
1
Извините, только что удалил мой комментарий, а потом увидел ваш ответ. У меня была проблема, потому что в моем реальном случае мой суперкласс - это класс Objective-C со strongсвойством, и я получал ошибку при попытке переопределить его, но, похоже, я пропустил, что он переводится как «неявно развернутый необязательный» ( chassis!) в Свифт, так override var chassis : Chassis!исправляет.
Джеймс
4
Это больше не работает со Swift 1.1 под Xcode 6.1. Генерирует ошибку: «Невозможно переопределить неизменяемое свойство let 'шасси' с получателем 'var'». Есть идеи для лучшего решения?
Даррарски
1
Также больше не работает в Swift 1.2 в бета-версии Xcode 6.3. Невозможно переопределить изменяемое свойство 'A' типа 'Type1' ковариантным типом 'Type2'
bubuxu
10
Это действительно отстой и провал Swift, имо. Поскольку RacingChassis - это шасси, у компилятора не должно возникнуть проблем, позволяющих вам уточнить класс свойства в подклассе. Многие языки позволяют это, и отсутствие поддержки приводит к подобным уродливым обходным путям. Без обид. : /
devios1
1
Необязательные параметры позволяют предположить, что переменная может отсутствовать, поскольку она была предоставлена ​​функцией, которая больше не возвращает ожидаемое значение. Такие случаи могут случиться, если функция была переопределена. Подробности читайте в моем ответе.
10

Кажется, это работает

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()
Дэвид Арв
источник
1
Это работает, поскольку свойство шасси определяется как letв Carклассе, что делает невозможным его изменение. Мы можем только изменить chassisсвойство RaceCarкласса.
David Arve
4
Это не работает для Swift 2.0. Выдает ошибку: невозможно переопределить неизменяемое свойство 'let' property'ssis 'с получателем' var '»
mohamede1945,
Это не работает и в Swift 4.0. Ошибка выполнения layground: ошибка: MyPlaygrounds.playground: 14: 15: ошибка: невозможно переопределить неизменяемое свойство 'let' property 'шасси с получателем' var 'переопределить var шасси: RacingChassis {^ MyPlaygrounds.playground: 11: 6: примечание : попытка переопределить свойство здесь letssis = Chassis () ^
karim
7

Попробуй это:

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.

adimtxg0422
источник
Ах, опрятный и чистый
Индер Кумар Ратхор
3

Предоставленный Solution Dash работает хорошо, за исключением того, что суперкласс должен быть объявлен с ключевым словом let, а не var. Вот решение, которое возможно, но НЕ РЕКОМЕНДУЕТСЯ!

Приведенное ниже решение будет компилироваться с Xcode 6.2, SWIFT 1.1 (если все классы находятся в разных быстрых файлах), но его следует избегать, потому что ЭТО МОЖЕТ ПРИВЕСТИ К НЕОЖИДАННОМУ ПОВЕДЕНИЮ (ВКЛЮЧАЯ АВАРИЯ, особенно при использовании необязательных типов). ПРИМЕЧАНИЕ: ЭТО НЕ РАБОТАЕТ С 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
        }
    }
}
Лукман
источник
1
Это не работает для Swift 2.0. Он дает "не может переопределить изменяемое свойство" шасси "типа" Шасси? " с ковариантным типом «RacingChassis?» »
mohamede1945,
2

Теоретически можно так делать ...

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.

Но если вы попробуете это в реальном проекте, ошибка компилятора сообщит вам:

Объявление 'view' не может перекрывать более одного объявления суперкласса

На данный момент я проверял только Xcode 6.0 GM.

К сожалению, вам придется подождать, пока Apple исправит это.

Я тоже отправил отчет об ошибке. 18518795

aleclarson
источник
Это интересное решение, оно работает в 6.1.
Крис Коновер,
1

Я видел множество причин, по которым разработка API с использованием переменных вместо функций является проблематичной, и для меня использование вычисленных свойств кажется обходным решением. Есть веские причины держать переменные экземпляра инкапсулированными. Здесь я создал автомобильный протокол, которому соответствует автомобиль. У этого протокола есть метод доступа, который возвращает объект Chassis. Поскольку Car соответствует ему, подкласс RaceCar может переопределить его и вернуть другой подкласс Chassis. Это позволяет классу Car программировать интерфейс (автомобиль), а класс RaceCar, который знает о RacingChassis, может напрямую обращаться к переменной _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
    }
}
Кори Хинтон
источник
1

Вы можете добиться этого с помощью дженериков:

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% безопасный код без принудительных развертываний.

Но это своего рода уловка, и если вам нужно будет переопределить несколько свойств, то объявления классов будут выглядеть как полный беспорядок. Так что будьте осторожны с этим подходом.

Аль Зонке
источник
1

В зависимости от того, как вы планируете использовать свойство, самый простой способ сделать это - использовать дополнительный тип для вашего подкласса и переопределить 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
        }
    }
}

Очевидно, вам нужно потратить некоторое время на проверку, чтобы убедиться, что классы могут быть инициализированы таким образом, но, установив для свойств значение optional, вы защитите себя от ситуаций, когда приведение больше не работает.

brandonscript
источник
0

Вы можете просто создать другую переменную 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и позволив нам получить / установить наш RacingChassisэкземпляр со chassisсвойством.
Джеймс
0

Попробуй это:

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()
    }
}
Жареный рис
источник
0
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())
    }
}
Домби Бенце
источник
0

Следующее позволяет использовать один объект в базовом и производном классах. В производном классе используйте свойство производного объекта.

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()
    }
}
Stickley
источник
0

Небольшое изменение других ответов, но проще и безопаснее с некоторыми приятными преимуществами.

class Chassis {}
class RacingChassis : Chassis {}

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

Преимущества заключаются в том, что нет ограничений на то, какое шасси должно быть (var, let, optional и т. Д.), А также легко создать подкласс RaceCar. Подклассы RaceCar могут иметь собственное вычисленное значение для шасси (или racingChassis).

Райан Х
источник
-2

просто установите новое свойство imageview с другим соглашением об именах, например imgview, потому что imageView уже является собственным свойством, и мы не можем назначить 2 сильных свойства.

Али Рафик
источник