Erro na classe Swift: propriedade não inicializada na chamada super.init

218

Eu tenho duas aulas ShapeeSquare

class Shape {
    var numberOfSides = 0
    var name: String
    init(name:String) {
        self.name = name
    }
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

class Square: Shape {
    var sideLength: Double

    init(sideLength:Double, name:String) {
        super.init(name:name) // Error here
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Com a implementação acima, recebo o erro:

property 'self.sideLength' not initialized at super.init call
    super.init(name:name)

Por que eu tenho que definir self.sideLengthantes de ligar super.init?

JuJoDi
fonte
certeza de que isso tem a ver com boas práticas de programação, não com uma restrição técnica real. Se o Shape chamar uma função que o Square substituiu, o Square pode querer usar sideLength, mas ainda não foi inicializado. Swift provavelmente apenas o impede de fazer isso por acidente, forçando-o a inicializar suas instâncias antes de chamar a classe base.
precisa saber é o seguinte
3
Os exemplos no livro são ruins. Você sempre deve chamar super.init () por último para garantir que todas as propriedades foram inicializadas, explicado abaixo.
Pascal

Respostas:

173

Cite a linguagem de programação Swift, que responde sua pergunta:

“O compilador da Swift realiza quatro verificações de segurança úteis para garantir que a inicialização em duas fases seja concluída sem erros:”

Verificação de segurança 1 “Um inicializador designado deve garantir que todas as“ propriedades introduzidas por sua classe sejam inicializadas antes de delegar um inicializador de superclasse ”.

Trecho de: Apple Inc. “A linguagem de programação Swift”. iBooks. https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11

Ruben
fonte
47
Agora essa é uma mudança impressionante em comparação com C ++, C # ou Java.
MDJ
12
@MDJ Coisa certa. Sinceramente, também não vejo o valor agregado disso.
Ruben
20
Na verdade, parece haver um. Digamos em C #, o construtor da superclasse não deve chamar nenhum método substituível (virtual), porque ninguém sabe como eles reagiriam com a subclasse não totalmente inicializada. Em Swift está ok, pois o estado extra da subclasse é bom, quando o construtor da superclasse está em execução. Além disso, em Swift, todos os métodos, exceto os finais, são substituíveis.
MDJ
17
Estou particularmente achando isso irritante, porque significa que, se eu quiser criar uma subclasse UIView que crie suas próprias subvisões, tenho que inicializar essas subvisões sem quadros para começar e adicionar os quadros mais tarde, pois não posso me referir à visão. até APÓS chamar super.init.
Ash
5
@Janos, se você tornar a propriedade opcional, não será necessário inicializá-la init.
JeremyP
105

O Swift tem uma sequência muito clara e específica de operações que são feitas nos inicializadores. Vamos começar com alguns exemplos básicos e avançar até um caso geral.

Vamos pegar um objeto A. Vamos defini-lo da seguinte forma.

class A {
    var x: Int
    init(x: Int) {
        self.x = x
    }
}

Observe que A não possui uma superclasse, portanto, não pode chamar uma função super.init () porque ela não existe.

OK, agora vamos subclassificar A com uma nova classe chamada B.

class B: A {
    var y: Int
    init(x: Int, y: Int) {
        self.y = y
        super.init(x: x)
    }
}

Este é um desvio do Objective-C, onde [super init]normalmente seria chamado primeiro antes de qualquer outra coisa. Não é assim em Swift. Você é responsável por garantir que suas variáveis ​​de instância estejam em um estado consistente antes de fazer qualquer outra coisa, incluindo métodos de chamada (que incluem o inicializador da sua superclasse).

Hitendra Solanki
fonte
1
isso é extremamente útil, um exemplo claro. obrigado!
FullMetalFist
O que se precisa o valor para calcular y, por exemplo: init (Y: Int) {self.y = Y * self.x super.init ()}
6rod9
1
use algo como, init (y: Int, x: Int = 0) {self.y = y * x; self.x = x; super.init (x: x)}, também você não pode chamar diretamente construtor vazio para o super classe com referência ao exemplo acima, porque os nomes de super classe A não tem construtor vazio
Hitendra Solanki
43

Dos documentos

Verificação de segurança 1

Um inicializador designado deve garantir que todas as propriedades introduzidas por sua classe sejam inicializadas antes de delegar um inicializador de superclasse.


Por que precisamos de uma verificação de segurança como esta?

Para responder a isso, vamos rapidamente ao processo de inicialização.

Inicialização bifásica

A inicialização de classe no Swift é um processo de duas fases. Na primeira fase, cada propriedade armazenada recebe um valor inicial pela classe que a introduziu. Depois que o estado inicial de cada propriedade armazenada é determinado, a segunda fase começa e cada classe recebe a oportunidade de personalizar ainda mais suas propriedades armazenadas antes que a nova instância seja considerada pronta para uso.

O uso de um processo de inicialização em duas fases torna a inicialização segura, enquanto ainda oferece flexibilidade completa a cada classe em uma hierarquia de classes. A inicialização em duas fases impede que os valores de propriedade sejam acessados ​​antes de serem inicializados e impede que valores de propriedade sejam configurados para um valor diferente por outro inicializador inesperadamente.

Portanto, para garantir que o processo de inicialização em duas etapas seja realizado conforme definido acima, há quatro verificações de segurança, uma delas é,

Verificação de segurança 1

Um inicializador designado deve garantir que todas as propriedades introduzidas por sua classe sejam inicializadas antes de delegar um inicializador de superclasse.

Agora, a inicialização em duas fases nunca fala sobre ordem, mas essa verificação de segurança é introduzida super.initapós a inicialização de todas as propriedades.

A verificação de segurança 1 pode parecer irrelevante, pois a inicialização em duas fases impede que os valores das propriedades sejam acessados ​​antes de serem inicializados , sem essa verificação de segurança 1.

Como nesta amostra

class Shape {
    var name: String
    var sides : Int
    init(sides:Int, named: String) {
        self.sides = sides
        self.name = named
    }
}

class Triangle: Shape {
    var hypotenuse: Int
    init(hypotenuse:Int) {
        super.init(sides: 3, named: "Triangle") 
        self.hypotenuse = hypotenuse
    }
}

Triangle.initfoi inicializado, todas as propriedades antes de serem usadas. Portanto, a verificação de segurança 1 parece irrelevante,

Mas então pode haver outro cenário, um pouco complexo,

class Shape {
    var name: String
    var sides : Int
    init(sides:Int, named: String) {
        self.sides = sides
        self.name = named
        printShapeDescription()
    }
    func printShapeDescription() {
        print("Shape Name :\(self.name)")
        print("Sides :\(self.sides)")
    }
}

class Triangle: Shape {
    var hypotenuse: Int
    init(hypotenuse:Int) {
        self.hypotenuse = hypotenuse
        super.init(sides: 3, named: "Triangle")
    }

    override func printShapeDescription() {
        super.printShapeDescription()
        print("Hypotenuse :\(self.hypotenuse)")
    }
}

let triangle = Triangle(hypotenuse: 12)

Resultado :

Shape Name :Triangle
Sides :3
Hypotenuse :12

Aqui, se tivéssemos chamado o super.initantes de definir o hypotenuse, a super.initchamada então chamaria o printShapeDescription()e, uma vez que isso foi substituído, ele retornaria primeiro à implementação da classe Triangle de printShapeDescription(). A printShapeDescription()classe of Triangle acessa hypotenuseuma propriedade não opcional que ainda não foi inicializada. E isso não é permitido, pois a inicialização em duas fases impede que os valores de propriedade sejam acessados ​​antes de serem inicializados.

Portanto, verifique se a inicialização em duas fases é feita conforme definido, precisa haver uma ordem específica de chamada super.init, ou seja, depois de inicializar todas as propriedades introduzidas pela selfclasse, precisamos de uma verificação de segurança 1

BangOperator
fonte
1
Ótima explicação, o porquê definitivamente deve ser adicionado à resposta superior.
Guy Daher
Então você está dizendo basicamente, porque a superclasse init pode chamar uma função (substituída) ... na qual essa função acessa a propriedade subclasses, para evitar não ter valores definidos, a chamada superdeve ocorrer após todos os valores serem definidos. OK faz sentido. Você quer saber como a Objective-C fez isso na época e por que você teve que ligar superprimeiro?
Mel
Essencialmente o que você está apontando para é semelhante a: colocação printShapeDescription() antes self.sides = sides; self.name = named; que geraria este erro: use of 'self' in method call 'printShapeDescription' before all stored properties are initialized. O erro do OP é fornecido para reduzir a 'possibilidade' de erro de tempo de execução.
Honey
Eu usei especificamente a palavra 'possibilidade', porque se printShapeDescriptionfosse uma função que não se referisse, selfisto é, algo como `print (" nothing "), então não haveria problemas. (No entanto, mesmo para que o compilador iria lançar um erro, porque não é super- inteligente)
Mel
Bem, objc não estava seguro. Swift é do tipo seguro, então objetos que não são opcionais precisam ser nulos!
Daij-Djan
36

O "super.init ()" deve ser chamado depois que você inicializar todas as suas variáveis ​​de instância.

No vídeo "Intermediate Swift" da Apple (você pode encontrá-lo na página de recursos de vídeo do desenvolvedor Apple https://developer.apple.com/videos/wwdc/2014/ ), por volta das 28:40, é explícito que todos os inicializadores em superclasse deve ser chamado DEPOIS de inicializar suas variáveis ​​de instância.

No Objetivo-C, foi o contrário. No Swift, como todas as propriedades precisam ser inicializadas antes de serem usadas, precisamos inicializar as propriedades primeiro. Isso visa impedir que uma chamada para a função substituída seja do método "init ()" da superclasse, sem inicializar as propriedades primeiro.

Portanto, a implementação do "Square" deve ser:

class Square: Shape {
    var sideLength: Double

    init(sideLength:Double, name:String) {
        self.sideLength = sideLength
        numberOfSides = 4
        super.init(name:name) // Correct position for "super.init()"
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}
Shuyang
fonte
1
Nunca teria adivinhado. Inicialização com super deve ser a primeira declaração é o que eu teria pensado. !! hm bastante mudança com rapidez.
Mythicalcoder
Por que isso precisa vir depois? Por favor, forneça um motivo técnico
Masih
14

Desculpe pela formatação feia. Basta colocar um caractere de pergunta após a declaração e tudo ficará bem. Uma pergunta informa ao compilador que o valor é opcional.

class Square: Shape {
    var sideLength: Double?   // <=== like this ..

    init(sideLength:Double, name:String) {
        super.init(name:name) // Error here
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Edit1:

Existe uma maneira melhor de ignorar esse erro. De acordo com o comentário do jmaschad, não há razão para usar o opcional no seu caso, pois os opcionais não são confortáveis ​​no uso e você sempre deve verificar se o opcional não é nulo antes de acessá-lo. Então, tudo que você precisa fazer é inicializar o membro após a declaração:

class Square: Shape {
    var sideLength: Double=Double()   

    init(sideLength:Double, name:String) {
        super.init(name:name)
        self.sideLength = sideLength
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Edit2:

Depois de dois pontos negativos nessa resposta, encontrei um caminho ainda melhor. Se você deseja que um membro da classe seja inicializado em seu construtor, deve atribuir um valor inicial a ele dentro do contratante e antes da chamada super.init (). Como isso:

class Square: Shape {
    var sideLength: Double  

    init(sideLength:Double, name:String) {
        self.sideLength = sideLength   // <= before super.init call..
        super.init(name:name)
        numberOfSides = 4
    }
    func area () -> Double {
        return sideLength * sideLength
    }
}

Boa sorte em aprender Swift.

fnc12
fonte
Apenas mude super.init(name:name)e self.sideLength = sideLength. Declarar sideLengthcomo opcional é enganoso e apresenta problemas adicionais mais tarde, quando você precisa forçar o desembrulhamento.
Johannes Luong
Sim, isso é uma opção. Graças
fnc12
Você realmente pode apenas ter var sideLength: Double, não há necessidade de atribuir-lhe um valor inicial
Jarsen
E se eu realmente tiver uma constante opcional. O que eu faço com isso? Preciso inicializar no construtor? Eu não vejo por que você tem que fazer isso, mas o compilador está reclamando em Swift 1.2
Van Du Tran
1
Perfeito! todas as três soluções funcionaram "?", "String ()", mas o problema para mim era que eu não havia 'atribuído' uma propriedade e, quando fiz isso, funcionou! Obrigado companheiro
Naishta 10/05
9

O swift obriga a inicializar cada var de membro antes que ele seja usado. Uma vez que não se pode ter certeza do que acontece quando é a hora da virada, ocorre um erro: é melhor prevenir do que remediar

Daij-Djan
fonte
1
Isso não faz sentido IMO porque a classe pai não deve ter visibilidade nas propriedades declaradas em seus filhos!
Andy Hin
1
Não, mas você pode substituir o material e começar a usar auto 'antes de super tornou'
Daij-Djan
Você pode ver aqui e os comentários que seguem? Acho que estou dizendo exatamente o que você está dizendo, ou seja, nós dois estamos dizendo que o compilador quer estar seguro em vez de desculpe, minha única pergunta é: então, como o objetivo-c resolveu esse problema? Ou não? Caso contrário, por que ainda exige que você escreva super.initna primeira linha?
Honey
7

Edward,

Você pode modificar o código no seu exemplo assim:

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}

Isso está usando um opcional implicitamente desembrulhado.

Na documentação, podemos ler:

"Assim como os opcionais, se você não fornecer um valor inicial ao declarar uma variável ou propriedade opcional implicitamente desembrulhada, seu valor será padronizado automaticamente como nulo".

Pavel Gubarev
fonte
Eu acho que essa é a opção mais limpa. Na minha primeira tentativa do Swift, eu tinha uma variável de membro do tipo AVCaptureDevice, que não pode ser instanciada diretamente, portanto, o código init () era necessário. No entanto, o ViewController requer vários inicializadores e você não pode chamar um método de inicialização comum a partir de init (); portanto, essa resposta parece ser a única opção que evita copiar / colar códigos duplicados em cada inicializador.
sunetos
6

Swift não permitirá que você inicialize superclasse sem inicializar as propriedades, ao contrário do Obj C. Portanto, você deve inicializar todas as propriedades antes de chamar "super.init".

Acesse http://blog.scottlogic.com/2014/11/20/swift-initialisation.html . Dá uma boa explicação para o seu problema.

jishnu bala
fonte
6

Adicione zero ao final da declaração.


// Must be nil or swift complains
var someProtocol:SomeProtocol? = nil

// Init the view
override init(frame: CGRect)
    super.init(frame: frame)
    ...

Isso funcionou para o meu caso, mas pode não funcionar para o seu

Michael
fonte
Bom, enquanto estiver usando com UIView com UIViewController com protocolo
abdul sathar 23/01
1

Você está apenas começando na ordem errada.

     class Shape2 {
        var numberOfSides = 0
        var name: String
        init(name:String) {
            self.name = name
        }
        func simpleDescription() -> String {
            return "A shape with \(numberOfSides) sides."
        }
    }

    class Square2: Shape2 {
        var sideLength: Double

        init(sideLength:Double, name:String) {

            self.sideLength = sideLength
            super.init(name:name) // It should be behind "self.sideLength = sideLength"
            numberOfSides = 4
        }
        func area () -> Double {
            return sideLength * sideLength
        }
    }
ylgwhyh
fonte
0

Provavelmente vou receber alguns votos negativos, mas, para ser sincero, a vida é mais fácil assim:

class CSListServerData<ListItem: CSJsonData>: CSServerData {
    var key: String!
    var type: ListItem.Type!
    var property: CSJsonDataList<ListItem>!

    func construct(_ key: String, _ type: ListItem.Type) -> Self {
        self.key = key
        self.type = type
        property = CSJsonDataList(self, type, key)
        return self
    }

    func construct(_ type: ListItem.Type) { construct("list", type) }

    var list: [ListItem] { property.list }
}
Renetik
fonte
-4

É um design incrivelmente estúpido.

Considere algo como isto:

.
.
.
var playerShip:PlayerShip
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100))
    playerLayerNode.addChild(playerShip)        
}
.
.
.

Isso é inválido, conforme observado acima. Mas assim é:

.
.
.
var playerShip:PlayerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100))
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}
.
.
.

Porque 'self' não foi inicializado.

Espero sinceramente que esse bug seja corrigido em breve.

(Sim, eu sei que eu poderia criar um objeto vazio e depois definir o tamanho, mas isso é estúpido).

Edward Kenworthy
fonte
2
Isso não é bug, sua nova programação é fundamental para os OOPs.
Hitendra Solanki