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.
- Se um protocolo especificar um inicializador como um método necessário, esse inicializador deverá ser marcado usando a
required
palavra-chave Swift .
- Swift tem um conjunto especial de regras de herança em relação a
init
métodos.
O tl; dr é o seguinte:
Se você implementar qualquer inicializador, não estará mais herdando nenhum dos inicializadores designados da superclasse.
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:
Diferentemente das subclasses no Objective-C, as subclasses Swift não herdam seus inicializadores de superclasse por padrão. A abordagem de Swift evita uma situação em que um inicializador simples de uma superclasse é herdado por uma subclasse mais especializada e é usado para criar uma nova instância da subclasse que não é inicializada total ou corretamente.
Ê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 1
Se sua subclasse não definir nenhum inicializador designado, ela herdará automaticamente todos os inicializadores designados da superclasse.
Regra 2
Se sua subclasse fornecer uma implementação de todos os inicializadores designados para sua superclasse - herdando-os de acordo com a regra 1 ou fornecendo uma implementação customizada como parte de sua definição -, ela herdará automaticamente todos os inicializadores de conveniência da superclasse.
Regra 2 não é particularmente relevante para esta conversa, porque SKSpriteNode
's init(coder: NSCoder)
é improvável que seja um método de conveniência.
Portanto, sua InfoBar
classe estava herdando o required
inicializador até o ponto que você adicionou init(team: Team, size: CGSize)
.
Se você não forneceu esse init
método e, em vez disso, tornou InfoBar
opcional as propriedades adicionadas, ou forneceu valores padrão, você ainda estaria herdando SKSpriteNode
as init(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:
class Foo {
var foo: String
init(foo: String) {
self.foo = foo
}
}
class Bar: Foo {
var bar: String
init(foo: String, bar: String) {
self.bar = bar
super.init(foo: foo)
}
}
let x = Bar(foo: "Foo")
Que apresenta o seguinte erro:
Argumento ausente para o parâmetro 'bar' na chamada.
Se fosse o Objective-C, não haveria problema em herdar. Se inicializamos a Bar
com initWithFoo:
em Objective-C, a self.bar
propriedade seria simplesmente nil
. 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 ser nil
.
Novamente, a única maneira de herdar inicializadores é não fornecer os nossos. Portanto, se tentarmos herdar excluindo Bar
's init(foo: String, bar: String)
, como tal:
class Bar: Foo {
var bar: String
}
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 :
Problema: a classe 'Bar' não possui inicializadores
Fix-It: Propriedade armazenada 'bar' sem inicializadores impede inicializadores sintetizados
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 é isso required
?
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 requer init
métodos especiais (como NSCoding
) exige que a classe marque esses init
métodos como required
.
Considere este exemplo:
protocol InitProtocol {
init(foo: Int)
}
class ConformingClass: InitProtocol {
var foo: Int
init(foo: Int) {
self.foo = foo
}
}
Isso não compila. Ele gera o seguinte aviso:
Problema: o requisito do inicializador 'init (foo :)' pode ser atendido apenas por um inicializador 'obrigatório' na classe não final 'ConformingClass'
Fix-It: inserção necessária
Ele quer que eu faça o init(foo: Int)
inicializador necessário. Eu também poderia torná-lo feliz fazendo a classe final
(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 o InitProtocol
. 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 a init
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 implementar init(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étodos required
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-C instanceType
). Mas, para fazer isso, eu realmente preciso usar um required
método inicializador.
class Box {
var size: CGSize
init(size: CGSize) {
self.size = size
}
class func factory() -> Self {
return self.init(size: CGSizeZero)
}
}
Isso gera o erro:
A construção de um objeto do tipo de classe 'Self' com um valor de metatype deve usar um inicializador 'required'
É basicamente o mesmo problema. Se subclassificarmos Box
, nossas subclasses herdarão o método de classe factory
. Para que pudéssemos ligar SubclassedBox.factory()
. No entanto, sem a required
palavra-chave no init(size:)
método, Box
não é garantido que as subclasses de herdem o self.init(size:)
que factory
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 um required
método inicializador e encontraremos exatamente os mesmos problemas que você encontrou aqui com o NSCoding
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 o required
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:
class Foo {
init(a: Int, b: Int, c: Int) {
// do nothing
}
}
class Bar: Foo {
init(string: String) {
super.init(a: 0, b: 1, c: 2)
// do more nothing
}
}
let f = Foo(a: 0, b: 1, c: 2)
let b = Bar(a: 0, b: 1, c: 2)
Isso falha ao compilar.
A mensagem de erro exibida é um pouco enganadora:
Argumento extra 'b' na chamada
Mas o ponto é, Bar
não herda qualquer um dos Foo
's init
métodos porque não satisfez nenhum dos dois casos especiais para herdar init
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.
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.