Tenho um protocolo que é restrito apenas a tipos de classe:
protocol Plugin: AnyObject {}
Agora, quero usar o plugin como chave do mapa de hash. Não quero que o Plugin
protocolo se estenda de Hashable
, porque teria que escrever any Plugin
em todos os lugares (já que ele herdaria "Auto-Requisito" do protocolo pai).
Então, para contornar isso, quero criar um wrapper genérico. Não quero usar AnyHashable
, porque quero um tipo mais restrito em caso de erros.
public struct ObjectHashable<T: AnyObject>: Hashable {
public let object: T
public init(object: T) {
self.object = object
}
public static func ==(lhs: Self, rhs: Self) -> Bool {
return ObjectIdentifier(lhs.object) == ObjectIdentifier(rhs.object)
}
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(object))
}
}
Agora eu quero fazer algo como
typealias PluginHashable = ObjectHashable<Plugin>
No entanto, isso me dá erro:
requisito especificado como 'T': 'AnyObject' [com T = qualquer plugin]
Então eu mudei para
typealias PluginHashable = ObjectHashable<any Plugin>
E recebi o mesmo erro:
requisito especificado como 'T': 'AnyObject' [com T = qualquer plugin]
Pelo que entendi, embora Plugin
o protocolo seja limitado a ser um tipo de classe, any Plugin
ele não é. No entanto, não sei o que fazer a seguir.
Atualizar:
Se eu não usar o genérico for ObjectHashable
, funciona:
public struct PluginHashable: Hashable {
public let plugin: Plugin
public init(plugin: Plugin) {
self.plugin = plugin
}
public static func ==(lhs: Self, rhs: Self) -> Bool {
return ObjectIdentifier(lhs.plugin) == ObjectIdentifier(rhs.plugin)
}
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(plugin))
}
}
No entanto, esta solução só é utilizável para Plugin
protocolo, portanto não é ideal. Prefiro ter uma solução que funcione para todos os casos semelhantes.
Eu entendo perfeitamente no que você está se metendo.
Mesmo que
Plugin: AnyObject
, quando você se refere aany Plugin
, ele seja tratado como um existencial — e existenciais em Swift não são tipos de classe , mesmo que o protocolo de onde eles vêm sejaAnyObject
.É por isso que
ObjectHashable<T: AnyObject>
se recusa a aceitarany Plugin
comoT
— porqueany Plugin
não é em si uma classe, mesmo que as implementações dePlugin
devam ser.Por que qualquer plugin é um problema
any Plugin é um tipo de valor que representa "qualquer instância em conformidade com Plugin".
Isso não garante que seja uma instância de classe no nível de tipo de uma forma que restrições genéricas possam verificar.
O Swift trata qualquer Plugin de forma diferente dos tipos concretos que estão em conformidade com o Plugin.
Então... como resolver sua situação?
Aqui está o truque: em vez de escrever
ObjectHashable<any Plugin>
,você precisa
ObjectHashable<some Plugin>
usar sites ouredesenhar
ObjectHashable
um pouco para aceitar valores existenciais.Uma solução limpa para o seu caso
Mude
ObjectHashable
para aceitar qualquer umAnyObject
(mesmoany Plugin
os existenciais).Aqui está uma versão ligeiramente atualizada de
ObjectHashable
:Agora você não precisa mais se preocupar com genéricos.
Você pode armazenar qualquer objeto de classe — inclusive
any Plugin
— encapsulado corretamente.Uso:
Mas você pediu rigor (não tão aberto assim
AnyObject
)...Se você ainda quiser restringir apenas ao Plugin (não a qualquer classe), veja como fazer isso de forma mais restrita:
- Isso garante que você só possa encapsular
any Plugin
, e não qualquerAnyObject
.- E você ainda faz o hash com base na identidade do objeto.
Uso:
Por que você não pode usar
ObjectHashable<Plugin>
?Porque
ObjectHashable<T: AnyObject>
espera um tipo de classeT
,mas
any Plugin
não é um tipo de classe — é um valor existencial.Você tem que:
fazer
ObjectHashable
não genérico, armazenarAnyObject
, OUespecialize-o para seu protocolo (como
PluginHashable
acima).Espero que isso ajude. Avise-me se o seu problema for resolvido.
EDITAR:
Já que você mencionou que essa solução anterior não funciona para você, vou seguir minha sugestão no comentário: especializar ObjectHashable para existenciais que são somente de classe.
Veja como você pode fazer isso:
Observe atentamente :
T
é genérico .Em tempo de execução, fazemos a conversão
AnyObject
manualmente para dentro==
ehash(into:)
.Em tempo de compilação , você ainda pode fazer uma verificação de tipo rigorosa para
ObjectHashable<Plugin>
etc.Agora o uso parece super limpo:
typealias PluginHashable = ObjectHashable<qualquer Plugin>
Com isso, você obtém:
- Aplicação rigorosa de tipos em tempo de compilação.
- Reutilizável também para outros protocolos somente de classe, sem precisar de um wrapper personalizado todas as vezes.
Caso você queira uma proteção de tempo de compilação ainda mais rigorosa, que seja opcional, você pode adicionar uma asserção de tempo de execução. Isso proibiria o uso indevido (por exemplo: alguém tentando passar uma struct). Você teria um código como este:
Mas, sinceramente, como você está escolhendo
any Plugin
oPluginHashable
alias manualmente, já está tudo bem. Não precisa verificar em tempo de execução, a menos que você seja superparanoico, rs.