Atualizei para o Xcode 6 beta 5 hoje e notei que recebi erros em quase todas as minhas subclasses de classes da Apple.
O erro declara:
A classe 'x' não implementa os membros obrigatórios de sua superclasse
Aqui está um exemplo que eu escolhi, porque essa classe é atualmente bastante leve, por isso será fácil postar.
class InfoBar: SKSpriteNode { //Error message here
let team: Team
let healthBar: SKSpriteNode
init(team: Team, size: CGSize) {
self.team = team
if self.team == Team.TeamGood {
healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
}
else {
healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
}
super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)
self.addChild(healthBar)
}
}
Portanto, minha pergunta é: por que estou recebendo esse erro e como corrigi-lo? O que é que eu não estou implementando? Estou ligando para um inicializador designado.
fonte
init(collection:MPMediaItemCollection)
. Você deve fornecer uma coleção real de itens de mídia; esse é o ponto desta classe. Esta classe simplesmente não pode ser instanciada sem uma. Ele vai analisar a coleção e inicializar uma dúzia de variáveis de instância. Esse é o ponto principal deste ser o único inicializador designado! Portanto,init(coder:)
não tem MPMediaItemCollection significativo (ou mesmo sem sentido) para fornecer aqui; apenas afatalError
abordagem está certa.init(collection:MPMediaItemCollection!)
. Isso permitiriainit(coder:)
passar nulo. Mas então eu percebi: não, agora você está apenas enganando o compilador. Passar nulo não é aceitável, então jogue ofatalError
e siga em frente. :)Existem duas partes absolutamente cruciais de informações específicas do Swift que estão faltando nas respostas existentes e acho que ajudam a esclarecer isso completamente.
required
palavra-chave Swift .init
métodos.O tl; dr é o seguinte:
Os únicos inicializadores, se houver, que você herdará, são inicializadores de conveniência de super classe que apontam para um inicializador designado que você substituiu.
Então ... pronto para a versão longa?
Swift tem um conjunto especial de regras de herança em relação a
init
métodos.Sei que esse foi o segundo de dois argumentos que fiz, mas não conseguimos entender o primeiro, ou por que a
required
palavra - chave existe até entendermos esse ponto. Uma vez que entendemos esse ponto, o outro se torna bastante óbvio.Todas as informações que abordo nesta seção desta resposta são da documentação da Apple encontrada aqui .
Dos documentos da Apple:
Ênfase minha.
Portanto, diretamente dos documentos da Apple, vemos que as subclasses Swift nem sempre (e geralmente não) herdam os
init
métodos de suas superclasses .Então, quando eles herdam da superclasse?
Existem duas regras que definem quando uma subclasse herda
init
métodos de seu pai. Dos documentos da Apple:Regra 2 não é particularmente relevante para esta conversa, porque
SKSpriteNode
'sinit(coder: NSCoder)
é improvável que seja um método de conveniência.Portanto, sua
InfoBar
classe estava herdando orequired
inicializador até o ponto que você adicionouinit(team: Team, size: CGSize)
.Se você não forneceu esse
init
método e, em vez disso, tornouInfoBar
opcional as propriedades adicionadas, ou forneceu valores padrão, você ainda estaria herdandoSKSpriteNode
asinit(coder: NSCoder)
. No entanto, quando adicionamos nosso próprio inicializador personalizado, paramos de herdar os inicializadores designados da superclasse (e inicializadores de conveniência que não apontam para os inicializadores que implementamos).Então, como um exemplo simplista, apresento o seguinte:
Que apresenta o seguinte erro:
Se fosse o Objective-C, não haveria problema em herdar. Se inicializamos a
Bar
cominitWithFoo:
em Objective-C, aself.bar
propriedade seria simplesmentenil
. Provavelmente não é grande, mas é um perfeitamente válido estado para o objeto a ser. É não um estado perfeitamente válido para o objeto Swift estar dentro.self.bar
Não é um opcional e não pode sernil
.Novamente, a única maneira de herdar inicializadores é não fornecer os nossos. Portanto, se tentarmos herdar excluindo
Bar
'sinit(foo: String, bar: String)
, como tal:Agora voltamos a herdar (mais ou menos), mas isso não será compilado ... e a mensagem de erro explica exatamente por que não herdamos os
init
métodos da superclasse :Se adicionamos propriedades armazenadas em nossa subclasse, não há maneira Swift possível de criar uma instância válida de nossa subclasse com os inicializadores de superclasse que não poderiam saber sobre as propriedades armazenadas de nossa subclasse.
Ok, bem, por que eu tenho que implementar
init(coder: NSCoder)
? Por que é issorequired
?Os
init
métodos de Swift podem desempenhar um conjunto especial de regras de herança, mas a conformidade com o protocolo ainda é herdada na cadeia. Se uma classe pai está em conformidade com um protocolo, suas subclasses devem estar em conformidade com esse protocolo.Normalmente, isso não é um problema, porque a maioria dos protocolos exige apenas métodos que não são executados por regras de herança especiais no Swift, portanto, se você está herdando de uma classe que está em conformidade com um protocolo, também está herdando todos os métodos ou propriedades que permitem à classe satisfazer a conformidade do protocolo.
No entanto, lembre-se, os
init
métodos de Swift seguem um conjunto especial de regras e nem sempre são herdados. Por esse motivo, uma classe que está em conformidade com um protocolo que requerinit
métodos especiais (comoNSCoding
) exige que a classe marque essesinit
métodos comorequired
.Considere este exemplo:
Isso não compila. Ele gera o seguinte aviso:
Ele quer que eu faça o
init(foo: Int)
inicializador necessário. Eu também poderia torná-lo feliz fazendo a classefinal
(o que significa que a classe não pode ser herdada).Então, o que acontece se eu subclasse? A partir deste ponto, se eu subclasse, estou bem. Se eu adicionar inicializadores, de repente, não estou mais herdando
init(foo:)
. Isso é problemático porque agora não estou mais em conformidade com oInitProtocol
. Não posso subclassificar de uma classe que esteja em conformidade com um protocolo e, de repente, decidir que não quero mais estar em conformidade com esse protocolo. Eu herdei a conformidade do protocolo, mas devido à maneira como o Swift trabalha com ainit
herança de métodos, não herdei parte do necessário para estar em conformidade com esse protocolo e preciso implementá-lo.Ok, tudo isso faz sentido. Mas por que não consigo receber uma mensagem de erro mais útil?
Indiscutivelmente, a mensagem de erro pode ser mais clara ou melhor se especificar que sua classe não está mais em conformidade com o
NSCoding
protocolo herdado e que, para corrigi-lo, é necessário implementarinit(coder: NSCoder)
. Certo.Mas o Xcode simplesmente não pode gerar essa mensagem, porque esse nem sempre será o problema real de não implementar ou herdar um método necessário. Há pelo menos um outro motivo para criar
init
métodosrequired
além da conformidade com o protocolo, e esses são métodos de fábrica.Se eu quiser escrever um método de fábrica adequado, preciso especificar o tipo de retorno a ser
Self
(o equivalente de Swift aos Objective-CinstanceType
). Mas, para fazer isso, eu realmente preciso usar umrequired
método inicializador.Isso gera o erro:
É basicamente o mesmo problema. Se subclassificarmos
Box
, nossas subclasses herdarão o método de classefactory
. Para que pudéssemos ligarSubclassedBox.factory()
. No entanto, sem arequired
palavra-chave noinit(size:)
método,Box
não é garantido que as subclasses de herdem oself.init(size:)
quefactory
está chamando.Portanto, precisamos criar esse método
required
se quisermos um método de fábrica como este, e isso significa que, se nossa classe implementar um método como este, teremos umrequired
método inicializador e encontraremos exatamente os mesmos problemas que você encontrou aqui com oNSCoding
protocolo.Por fim, tudo se resume ao entendimento básico de que os inicializadores do Swift jogam com um conjunto de regras de herança um pouco diferente, o que significa que você não tem a garantia de herdar inicializadores da sua superclasse. Isso acontece porque os inicializadores de superclasse não podem conhecer suas novas propriedades armazenadas e não conseguiram instanciar seu objeto em um estado válido. Mas, por várias razões, uma superclasse pode marcar um inicializador como
required
. Quando isso acontece, podemos empregar um dos cenários muito específicos pelos quais realmente herdamos orequired
método ou devemos implementá-lo nós mesmos.O ponto principal aqui é que, se estamos recebendo o erro que você vê aqui, significa que sua classe não está realmente implementando o método.
Como talvez um exemplo final para aprofundar o fato de que as subclasses Swift nem sempre herdam os
init
métodos de seus pais (o que eu acho absolutamente essencial para entender completamente esse problema), considere este exemplo:Isso falha ao compilar.
A mensagem de erro exibida é um pouco enganadora:
Mas o ponto é,
Bar
não herda qualquer um dosFoo
'sinit
métodos porque não satisfez nenhum dos dois casos especiais para herdarinit
métodos de sua classe pai.Se fosse o Objective-C, herdaríamos isso
init
sem problemas, porque o Objective-C é perfeitamente feliz por não inicializar as propriedades dos objetos (embora, como desenvolvedor, você não devesse estar satisfeito com isso). Em Swift, isso simplesmente não funciona. Você não pode ter um estado inválido e a herança de inicializadores de superclasse pode levar apenas a estados de objetos inválidos.fonte
Por que esse problema surgiu? Bem, o fato claro é que sempre foi importante (ou seja, no Objective-C, desde o dia em que comecei a programar o Cocoa no Mac OS X 10.0) para lidar com inicializadores que sua classe não está preparada para lidar. Os documentos sempre foram bastante claros sobre suas responsabilidades nesse sentido. Mas quantos de nós se preocuparam em cumpri-las, completamente e de acordo com a letra? Provavelmente nenhum de nós! E o compilador não os aplicou; tudo era puramente convencional.
Por exemplo, na minha subclasse de controlador Objective-C view com este inicializador designado:
... é crucial que recebamos uma coleção real de itens de mídia: a instância simplesmente não pode existir sem uma. Mas não escrevi nenhuma "rolha" para impedir que alguém me inicializasse com os ossos
init
. Eu deveria ter escrito um (na verdade, falando corretamente, eu deveria ter escrito uma implementação doinitWithNibName:bundle:
inicializador designado herdado); mas eu estava com preguiça de me preocupar, porque "sabia" que nunca inicializaria incorretamente minha própria classe dessa maneira. Isso deixou um buraco. No Objetivo-C, alguém pode chamar deinit
esqueleto, deixando meus ivars não inicializados, e nós estamos subindo o riacho sem remo.Swift, maravilhosamente, me salva de mim mesmo na maioria dos casos. Assim que traduzi este aplicativo para o Swift, o problema foi resolvido. Swift efetivamente cria uma rolha para mim! Se
init(collection:MPMediaItemCollection)
é o único inicializador designado declarado na minha classe, não posso ser inicializado chamando bare-bonesinit()
. É um milagre!O que aconteceu na semente 5 é apenas que o compilador percebeu que o milagre não funciona no caso de
init(coder:)
, porque, em teoria, uma instância dessa classe pode vir de uma ponta, e o compilador não pode impedir isso - e quando o cargas de ponta,init(coder:)
serão chamadas. Portanto, o compilador faz você escrever a rolha explicitamente. E muito certo também.fonte
fatalError
rolha descrita em stackoverflow.com/a/25128815/341994 . Basta torná-lo um snippet de código do usuário e, a partir de agora, você pode inseri-lo onde for necessário. Leva meio segundo.adicionar
fonte