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

126

Я заметил, что компилятор не позволяет мне переопределить сохраненное свойство другим сохраненным значением (что кажется странным):

class Jedi {
    var lightSaberColor = "Blue"
}


class Sith: Jedi {
    override var lightSaberColor = "Red" // Cannot override with a stored property lightSaberColor
}

Однако мне разрешено делать это с помощью вычисляемого свойства:

class Jedi {
    let lightSaberColor = "Blue"
}


class Sith: Jedi {
    override var lightSaberColor : String{return "Red"}

}

Почему мне не разрешено присвоить ему другое значение?

Почему переопределение сохраненного свойства является мерзостью, а выполнение этого - кошерным? Что где они думают?

cfischer
источник
1
возможный дубликат свойства Overriding суперкласса с другим типом в Swift
Макс МакЛауд

Ответы:

82

Почему мне не разрешено просто придать ему другое значение?

Вы определенно можете присвоить унаследованному свойству другое значение. Вы можете сделать это, если инициализируете свойство в конструкторе, который принимает это начальное значение, и передаете другое значение из производного класса:

class Jedi {
    // I made lightSaberColor read-only; you can make it writable if you prefer.
    let lightSaberColor : String
    init(_ lsc : String = "Blue") {
        lightSaberColor = lsc;
    }
}

class Sith : Jedi {
    init() {
        super.init("Red")
    }
}

let j1 = Jedi()
let j2 = Sith()

println(j1.lightSaberColor)
println(j2.lightSaberColor)

Переопределение свойства - это не то же самое, что присвоение ему нового значения - это больше похоже на присвоение классу другого свойства. Фактически, именно это и происходит, когда вы переопределяете вычисляемое свойство: код, вычисляющий свойство в базовом классе, заменяется кодом, который вычисляет переопределение для этого свойства в производном классе.

[Возможно ли] переопределить фактическое сохраненное свойство, т. lightSaberColorЕ. Имеющее другое поведение?

За исключением наблюдателей, сохраненные свойства не имеют поведения, поэтому там действительно нечего переопределять. Присвоение собственности другого значения возможно с помощью описанного выше механизма. Это именно то, что пытается достичь пример в вопросе, с другим синтаксисом.

dasblinkenlight
источник
2
@MaxMacLeod За исключением наблюдателей, сохраненные свойства не имеют поведения, поэтому там действительно нечего переопределять. Он хотел присвоить хранимому свойству другое значение в подклассе, но не был уверен в механизме его достижения. Ответ объясняет, как это можно сделать в Swift. Прошу прощения за поздний ответ, похоже, что ваш комментарий вызывает достаточно путаницы, чтобы привлечь отрицательные голоса, поэтому я решил объяснить, что происходит.
dasblinkenlight 09
55

Для меня ваш пример не работает в Swift 3.0.1.

Я ввел на детской площадке этот код:

class Jedi {
    let lightsaberColor = "Blue"
}

class Sith: Jedi {
    override var lightsaberColor : String {
        return "Red"
    }
}

Выдает ошибку во время компиляции в Xcode:

не может переопределить неизменное свойство let 'lightsaberColor' с получателем 'var'

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

Но если вы измените его на varи, следовательно, добавите setв вычисляемое свойство, вы можете переопределить сохраненное свойство вычисляемым свойством того же типа.

class Jedi {
    var lightsaberColor = "Blue"
}


class Sith: Jedi {
    override var lightsaberColor : String {
        get {
            return "Red"
        }
        set {
            // nothing, because only red is allowed
        }
    }
}

Это возможно, потому что имеет смысл переключиться с сохраненного свойства на вычисляемое.

Но переопределение сохраненного varсвойства на сохраненное varсвойство не имеет смысла, потому что вы можете изменить только значение свойства.

Однако вы можете вообще не заменять сохраненное свойство сохраненным свойством.


Я бы не сказал, что ситхи - джедаи :-P. Поэтому ясно, что это не может работать.

Binarian
источник
18
class SomeClass {
    var hello = "hello"
}
class ChildClass: SomeClass {
    override var hello: String {
        set {
            super.hello = newValue
        }
        get {
            return super.hello
        }    
    }
}
Чжипин Ян
источник
13
Хотя этот фрагмент кода может решить вопрос, включение объяснения действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причины вашего предложения кода.
DimaSan
15

Вероятно, вы захотите присвоить этому свойству другое значение:

class Jedi {
    var lightSaberColor = "Blue"
}


class Sith: Jedi {
    override init() {
        super.init()
        self.lightSaberColor = "Red"
    }
}
zisoft
источник
9
То же самое относится к комментарию выше
Макс МакЛауд
10

Для Swift 4 из документации Apple :

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

riyasavla
источник
7

В Swift это, к сожалению, невозможно. Лучшая альтернатива следующая:

class Jedi {
    private(set) var lightsaberColor = "Blue"
}


class Sith: Jedi {
    override var lightsaberColor : String {
        get {
            return "Red"
        }
    }
}
Ной Уайлдер
источник
3

У меня была такая же проблема, чтобы установить константу для контроллера представления.

Поскольку я использую построитель интерфейса для управления представлением, я не могу его использовать init(), поэтому мой обходной путь был аналогичен другим ответам, за исключением того, что я использовал вычисляемую переменную только для чтения как в базовом, так и в унаследованном классах.

class Jedi {
    var type: String {
        get { return "Blue" }
    }
}

class Sith: Jedi {
    override var type: String {
        get { return "Red" }
    }
}
Ясин Эль-Бадауи
источник
3

Если вы попытаетесь сделать это в Swift 5, вас встретит

Невозможно переопределить неизменяемое свойство let 'lightSaberColor' с помощью получателя 'var'

Лучше всего объявить его как вычисляемое свойство.

Это работает, поскольку мы просто переопределяем get {}функцию

class Base {
   var lightSaberColor: String { "base" }
}

class Red: Base {
   override var lightSaberColor: String { "red" }
}
Уго Алонсо
источник
2

Swift не позволяет вам переопределить переменную. stored propertyВместо этого вы можете использоватьcomputed property

class A {
    var property1 = "A: Stored Property 1"

    var property2: String {
        get {
            return "A: Computed Property 2"
        }
    }

    let property3 = "A: Constant Stored Property 3"

    //let can not be a computed property
    
    func foo() -> String {
        return "A: foo()"
    }
}

class B: A {

    //now it is a computed property
    override var property1: String {

        set { }
        get {
            return "B: overrode Stored Property 1"
        }
    }

    override var property2: String {
        get {
            return "B: overrode Computed Property 2"
        }
    }
    
    override func foo() -> String {
        return "B: foo()"
    }

    //let can not be overrode
}
func testPoly() {
    let a = A()
    
    XCTAssertEqual("A: Stored Property 1", a.property1)
    XCTAssertEqual("A: Computed Property 2", a.property2)
    
    XCTAssertEqual("A: foo()", a.foo())
    
    let b = B()
    XCTAssertEqual("B: overrode Stored Property 1", b.property1)
    XCTAssertEqual("B: overrode Computed Property 2", b.property2)
    
    XCTAssertEqual("B: foo()", b.foo())
    
    //B cast to A
    XCTAssertEqual("B: overrode Stored Property 1", (b as! A).property1)
    XCTAssertEqual("B: overrode Computed Property 2", (b as! A).property2)
    
    XCTAssertEqual("B: foo()", (b as! A).foo())
}

Это более очевидно при сравнении с Java, где поле класса не может быть переопределено и не поддерживает полиморфизм, потому что определяется во время компиляции (выполняется эффективно). Это называется скрытием переменной [О программе] Не рекомендуется использовать эту технику, потому что ее трудно читать / поддерживать

[Свойство Swift]

yoAlex5
источник
1

Вы также можете использовать функцию для переопределения. Это не прямой ответ, но может обогатить эту тему)

Класс А

override func viewDidLoad() {
    super.viewDidLoad()

    if shouldDoSmth() {
       // do
    }
}

public func shouldDoSmth() -> Bool {
    return true
}

Класс B: A

public func shouldDoSmth() -> Bool {
    return false
}
Ник Ков
источник