Переосмислення властивості надкласу з різним типом у 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. Генерує помилку: "Неможливо змінити незмінний 'нехай' властивість 'шасі' за допомогою getter 'var'". Будь-які ідеї для кращого рішення?
Даррарський

1
Також більше не працює у Swift 1.2 під бета-версією Xcode 6.3. Неможливо змінити змінене властивість 'A' типу 'Type1' з коваріантним типом 'Type2'
bubuxu

10
Це справді кульгаво, і невдача Свіфта, imo. Оскільки 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класі.
Девід Арве

4
Це не працює для Swift 2.0. Це дає "помилку: не вдається перемогти незмінний" нехай "шасі" властивості "з
гетьтером

Це також не працює в швидкому 4.0. Не вдалося виконати шару: помилка: MyPlaygrounds.playground: 14: 15: помилка: не вдається змінити незмінний 'шасі' властивості 'шасі' з отриманням шасі 'var' переосмислити вар: RacingChassis {^ MyPlaygrounds.playground: 11: 6: note : спроба змінити властивість тут нехай chassis = 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


Ак акуратний і чистий
Індэр Кумар Rathore

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.

Але якщо ви спробуєте це в реальному проекті, помилка компілятора говорить вам:

Декларація "перегляд" не може замінити більш ніж одну декларацію про суперклас

Я перевірив Xcode 6.0 GM на даний момент.

На жаль, вам доведеться почекати, поки Apple це виправить.

Я також подав звіт про помилку. 18518795


Це цікаве рішення, і воно працює в 6.1.
Кріс Коновер

1

Я бачив багато причин, чому проектування API з використанням змінних замість функцій є проблематичним, і для мене використання обчислюваних властивостей відчуває себе як вирішення. Є вагомі причини, щоб ваші змінні екземпляри були інкапсульовані. Тут я створив протокол Automobile, якому відповідає Автомобіль. Цей протокол має метод accessor, який повертає об'єкт Chassis. Оскільки автомобіль відповідає йому, підклас RaceCar може змінити його і повернути інший підклас Шасі. Це дозволяє класу Car програмувати інтерфейс (Automobile), а клас 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
        }
    }
}

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


0

Ви можете просто створити іншу змінну RacingChassis.

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

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

Це працює, але я думаю, що відповідь Даша робить цей крок далі, змінюючи 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()
    }
}

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

просто встановіть нову властивість перегляду зображень з іншою умовою іменування, як imgview, тому що imageView вже є власною властивістю, і ми не можемо призначити 2 сильні властивості.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.