Substituindo a propriedade da superclasse por um tipo diferente no Swift

129

Em Swift, alguém pode explicar como substituir uma propriedade em uma superclasse por outro objeto subclassificado da propriedade original?

Veja este exemplo simples:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override let chassis = RacingChassis() //Error here
}

Isso dá o erro:

Cannot override with a stored property 'chassis'

Se eu tiver chassi como 'var', recebo o erro:

Cannot override mutable property 'chassis' of type 'Chassis' with covariant type 'RacingChassis'

A única coisa que encontrei no guia em "Substituindo propriedades" indica que precisamos substituir o getter e o setter, o que pode funcionar para alterar o valor da propriedade (se for 'var'), mas e quanto à alteração da classe de propriedade ?

James
fonte

Respostas:

115

Swift não permite alterar o tipo de classe de nenhuma variável ou propriedade. Em vez disso, você pode criar uma variável extra na subclasse que lida com o novo tipo de classe:

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")
            }
        }
    }
}

Parece que não se pode declarar uma propriedade com a sintaxe let e substituí-la por var em sua subclasse ou vice-versa, o que pode ser porque a implementação da superclasse pode não estar esperando que essa propriedade mude depois de inicializada. Portanto, nesse caso, a propriedade precisa ser declarada com 'var' na superclasse e também para corresponder à subclasse (como mostrado no snippet acima). Se não for possível alterar o código fonte na superclasse, é provavelmente melhor destruir o RaceCar atual e criar um novo RaceCar toda vez que o chassi precisar ser alterado.

Traço
fonte
1
Desculpe, excluí meu comentário e vi sua resposta. Eu estava com um problema porque, no meu caso real, minha superclasse é uma classe Objective-C com uma strongpropriedade e estava recebendo um erro ao tentar substituí-la - mas parece que perdi que isso se traduz em um "opcional implicitamente desembrulhado" ( chassis!) em Swift, então override var chassis : Chassis!conserta.
James
4
Isso não funciona mais com o Swift 1.1 no Xcode 6.1. Gera erro: "Não é possível substituir o imutável 'deixe' propriedade 'chassi' com o getter de um 'var'". Alguma idéia para uma melhor solução?
Darrarski
1
Também não funciona mais no Swift 1.2 no Xcode 6.3 beta. Não pode substituir a propriedade mutável 'A' do tipo 'Type1' com o tipo covariant 'Type2'
bubuxu
10
Isso é realmente manco, e uma falha de Swift, imo. Como o RacingChassis é um chassi, o compilador não deve ter problemas em permitir que você refine a classe da propriedade em uma subclasse. Muitos idiomas permitem isso, e não suportá-lo leva a soluções feias como essa. Sem ofensa. : /
devios1
1
Os opcionais fornecem um meio de sugerir que uma variável pode não estar presente porque foi fornecida por uma função que não retorna mais o valor esperado. Esses casos podem ocorrer se a função foi substituída. Dê uma olhada na minha resposta para obter detalhes.
10

Isso parece funcionar

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()
David Arve
fonte
1
Isso funciona como a propriedade do chassi definida como letna Carclasse que impossibilita a alteração. Só podemos alterar a chassispropriedade na RaceCarclasse.
David Arve
4
Isso não funciona no Swift 2.0. Dá "erro: não é possível substituir o imutável 'deixe' propriedade 'chassi' com o getter de um 'var'"
mohamede1945
Isso também não funciona no veloz 4.0. falha na execução do layout: erro: MyPlaygrounds.playground: 14: 15: erro: não é possível substituir o chassi imutável 'let' property '' com o getter de um chassi 'var' override var: RacingChassis {^ MyPlaygrounds.playground: 11: 6: note : tentativa de substituir a propriedade aqui deixe chassis = Chassis () ^ #
karim
7

Tente o seguinte:

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
     } 
}

Então:

let racingChassis = RacingChassis()
let raceCar = RaceCar(chassis:racingChassis)
print(raceCar.description) //output:racing

Detalhe em http://www.mylonly.com/14957025459875.html

adimtxg0422
fonte
Ah arrumado e limpo
Inder Kumar Rathore
3

O Solution Dash fornecido funciona bem, exceto que a superclasse deve ser declarada com a palavra-chave let em vez de var. Aqui está uma solução que é possível, mas NÃO RECOMENDADA!

A solução abaixo será compilada com o Xcode 6.2, SWIFT 1.1 (se todas as classes estiverem em arquivos swift diferentes), mas deve ser evitada porque PODE LEVAR A COMPORTAMENTOS INESPERADOS (INCLUINDO UM CRASH, especialmente ao usar tipos não opcionais). NOTA: ISSO NÃO FUNCIONA COM O 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
        }
    }
}
Lukman
fonte
1
Não funciona com o Swift 2.0. Ele fornece "não é possível substituir a propriedade mutável 'chassi' do tipo 'Chassis?' com tipo covariante 'RacingChassis?' "
mohamede1945
2

Teoricamente, você pode fazê-lo desta maneira ...

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! }
}

Isso funciona perfeitamente em um playground do Xcode.

Mas , se você tentar fazer isso em um projeto real, um erro do compilador informará:

A declaração 'view' não pode substituir mais de uma declaração de superclasse

Eu só verifiquei o Xcode 6.0 GM a partir de agora.

Infelizmente, você terá que esperar até que a Apple resolva isso.

Também enviei um relatório de erro. 18518795

aleclarson
fonte
Esta é uma solução interessante e funciona em 6.1.
Chris Conover
1

Eu já vi várias razões pelas quais projetar uma API usando variáveis ​​em vez de funções é problemático e, para mim, usar propriedades computadas parece uma solução alternativa. Existem boas razões para manter suas variáveis ​​de instância encapsuladas. Aqui eu criei um protocolo Automóvel com o qual o carro está em conformidade. Este protocolo possui um método acessador que retorna um objeto Chassis. Como o carro está em conformidade, a subclasse RaceCar pode substituí-lo e retornar uma subclasse Chassis diferente. Isso permite que a classe Car programe para uma interface (Automóvel) e a classe RaceCar que conhece o RacingChassis pode acessar diretamente a variável _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
    }
}

Outro exemplo de por que o design de uma API usando variáveis ​​é interrompido é quando você tem variáveis ​​em um protocolo. Se você quiser dividir todas as funções de protocolo em extensões, você pode, exceto que as propriedades armazenadas não podem ser colocadas em extensões e precisam ser definidas na classe (para compilar isso, você precisa descomentar o código em Classe AdaptableViewController e remova a variável mode da extensão):

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
    }
}

O código acima terá este erro do compilador: "As extensões podem não ter propriedades armazenadas". Aqui está como você pode reescrever o exemplo acima, para que tudo no protocolo possa ser separado na extensão usando as funções:

protocol Adaptable {
    func mode() -> Int
    func adapt()
}

class AdaptableViewController: UIViewController {
}

extension AdaptableViewController: Adaptable {
    func mode() -> Int {
        return 0
    }
    func adapt() {
        // adapt code
    }
}
Korey Hinton
fonte
1

Você pode conseguir isso com o uso de genéricos:

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

Com essa abordagem, você terá 100% de código de segurança, sem forçar a abertura.

Mas isso é meio que um hack e, se você precisar redefinir várias propriedades, as declarações de classe parecerão uma bagunça total. Portanto, tenha cuidado com essa abordagem.

Al Zonke
fonte
1

Dependendo de como você planeja usar a propriedade, a maneira mais simples de fazer isso é usar um tipo opcional para sua subclasse e substituir o didSet {}método para a super:

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
        }
    }
}

Obviamente, você precisa gastar algum tempo verificando para garantir que as classes possam ser inicializadas dessa maneira, mas, configurando as propriedades como opcional, você se protege contra situações nas quais a transmissão não funciona mais.

brandonscript
fonte
0

Você pode simplesmente criar outra variável do RacingChassis.

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

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}
Anton
fonte
Isso funciona, mas acho que a resposta do Dash leva esse passo adiante, substituindo chassise permitindo que obtenhamos / configuremos nossa RacingChassisinstância com a chassispropriedade
James
0

Tente o seguinte:

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()
    }
}
Arroz frito
fonte
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())
    }
}
Dombi Bence
fonte
0

A seguir, é possível usar um único objeto nas classes base e derivada. Na classe derivada, use a propriedade do objeto derivado.

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
fonte
0

Uma ligeira variação de outras respostas, mas mais simples e segura, com alguns benefícios interessantes.

class Chassis {}
class RacingChassis : Chassis {}

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

Os benefícios incluem que não há restrição sobre o que o chassi deve ser (var, let, opcional etc.), e é fácil subclassificar o RaceCar. As subclasses do RaceCar podem ter seu próprio valor calculado para o chassi (ou racingChassis).

Ryan H
fonte
-2

basta definir uma nova propriedade imageview com uma convenção de nomenclatura diferente, como imgview, porque o imageView já é de sua propriedade e não podemos atribuir duas propriedades fortes.

Ali Rafiq
fonte