Como posso fazer uma referência de protocolo fraca no Swift 'puro' (sem @objc)

561

weakas referências parecem não funcionar no Swift, a menos que a protocolseja declarado como @objc, o que não desejo em um aplicativo Swift puro.

Este código fornece um erro de compilação ( weaknão pode ser aplicado ao tipo que não é de classe MyClassDelegate):

class MyClass {
  weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate {
}

Preciso prefixar o protocolo com @objc, então ele funciona.

Pergunta: Qual é a maneira rápida 'pura' de realizar um weak delegate?

hnh
fonte
fazer nota ... stackoverflow.com/a/60837041/294884
Fattie

Respostas:

1038

Você precisa declarar o tipo do protocolo como AnyObject.

protocol ProtocolNameDelegate: AnyObject {
    // Protocol stuff goes here
}

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

Usando AnyObjectvocê, você diz que apenas classes podem estar em conformidade com este protocolo, enquanto estruturas ou enumerações não.

flainez
fonte
25
Meu problema com essas soluções é que chamar o delegado causa uma falha - EXC_BAD_ACCESS (conforme observado por outras pessoas em outros lugares). Isso parece ser um bug. A única solução que encontrei é usar o @objc e eliminar todos os tipos de dados Swift do protocolo.
Jim T
12
Qual é a maneira correta de fazer delegados fracos agora no Swift? A documentação da Apple não está mostrando ou declarando o delegado como fraco no código de exemplo: developer.apple.com/library/ios/documentation/swift/conceptual/…
C0D3
2
Isso nem sempre é seguro - lembre-se de que você só precisa tornar o delegado fraco se ele também tiver uma referência ao delegador e você precisar interromper esse forte ciclo de referência. Se o delegado não fizer referência ao delegador, ele poderá ficar fora do escopo (porque é fraco) e você terá falhas e outros problemas: / algo a ter em mente.
Trev14
5
BTW: Eu acho que o "novo estilo" (Swift 5) serve protocol ProtocolNameDelegate: AnyObject, mas não importa.
Hnh 30/04/19
1
Deve ser AnyObjectporque classserá descontinuado em algum momento.
José
283

Resposta complementar

Sempre fiquei confuso sobre se os delegados deveriam ser fracos ou não. Recentemente, aprendi mais sobre os delegados e quando usar referências fracas, então deixe-me acrescentar alguns pontos adicionais aqui para o bem dos futuros telespectadores.

  • O objetivo do uso da weakpalavra-chave é evitar ciclos de referência fortes (ciclos de retenção). Ciclos de referência fortes acontecem quando duas instâncias de classe têm referências fortes uma à outra. Suas contagens de referência nunca chegam a zero e nunca são desalocadas.

  • Você só precisa usar weakse o delegado for uma classe. Estruturas e enumerações rápidas são tipos de valor (seus valores são copiados quando uma nova instância é criada), não tipos de referência, portanto, eles não fazem ciclos de referência fortes .

  • weakas referências são sempre opcionais (caso contrário você usaria unowned) e sempre var(não let) para que o opcional possa ser definido como nilquando for desalocado.

  • Uma classe pai deve naturalmente ter uma forte referência a suas classes filho e, portanto, não usar a weakpalavra - chave. Porém, quando uma criança deseja uma referência ao pai, ela deve ser uma referência fraca usando a weakpalavra - chave.

  • weakdeve ser usado quando você quiser uma referência a uma classe que não é sua, não apenas para um filho que faz referência ao pai. Quando duas classes não hierárquicas precisam fazer referência uma à outra, escolha uma para ser fraca. O que você escolher depende da situação. Veja as respostas a esta pergunta para mais informações.

  • Como regra geral, os delegados devem ser marcados comoweak porque a maioria dos delegados está referenciando classes que não são de sua propriedade. Definitivamente, isso é verdade quando uma criança está usando um delegado para se comunicar com os pais. Usar uma referência fraca para o delegado é o que a documentação recomenda. (Mas veja isso também.)

  • Os protocolos podem ser usados ​​para os tipos de referência (classes) e tipos de valor (estruturas, enumerações). Portanto, no provável caso de você precisar tornar um delegado fraco, é necessário torná-lo um protocolo somente de objeto. A maneira de fazer isso é adicionar AnyObjectà lista de herança do protocolo. (No passado, você fazia isso usando a classpalavra - chave, mas AnyObjecté preferível agora .)

    protocol MyClassDelegate: AnyObject {
        // ...
    }
    
    class SomeClass {
        weak var delegate: MyClassDelegate?
    }

Um estudo mais aprofundado

A leitura dos artigos a seguir foi o que me ajudou a entender isso muito melhor. Eles também discutem questões relacionadas, como a unownedpalavra - chave e os fortes ciclos de referência que acontecem com os fechamentos.

Relacionado

Suragch
fonte
5
Tudo isso é agradável e interessante, mas não está realmente relacionado à minha pergunta original - que não é sobre fraco / o próprio ARC nem sobre por que os delegados geralmente são fracos. Já sabemos de tudo isso e nos perguntamos como você pode declarar uma referência de protocolo fraca (respondida perfeitamente pelo @flainez).
hnh 2/01/16
30
Você está certo. Na verdade, eu tinha a mesma pergunta que você anteriormente, mas estava faltando muitas dessas informações básicas. Fiz a leitura acima e fiz as anotações adicionais para me ajudar a entender todos os problemas relacionados à sua pergunta. Agora, acho que posso aplicar sua resposta aceita e sei por que estou fazendo isso. Espero que talvez ajude futuros espectadores também.
Suragch
5
Mas posso ter um protocolo fraco que NÃO dependa do tipo? Um protocolo por si mesmo não se importa com o objeto em conformidade. Portanto, tanto uma classe quanto uma estrutura podem estar em conformidade com ela. Ainda é possível ter o benefício de ambos poderem se adaptar a ele, mas apenas os tipos de classe que se conformam são fracos?
FlowUI. SimpleUITesting.com 06/07/2016
> porque a maioria dos delegados está referenciando classes que eles não possuem, eu os reescreveria como: maioria dos delegadores. Caso contrário, o objeto não-proprietário se tornará o proprietário
Victor Jalencas
36

AnyObject é a maneira oficial de usar uma referência fraca no Swift.

class MyClass {
    weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate: AnyObject {
}

Da Apple:

Para evitar ciclos de referência fortes, os delegados devem ser declarados como referências fracas. Para obter mais informações sobre referências fracas, consulte Ciclos de referência fortes entre instâncias de classe. Marcar o protocolo como somente classe posteriormente permitirá que você declare que o delegado deve usar uma referência fraca. Você marca um protocolo como sendo somente classe, herdando de AnyObject , conforme discutido em Protocolos Somente Classe.

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID276

Tim Chen
fonte
7
Interessante. Foi classdescontinuado no Swift 4.1?
hnh 19/02/19
@hnh Você ainda pode criar um "pseudo-protocolo" transformando-o em classe, mas protocolo: AnyObject faz exatamente o que o OP está pedindo, com menos efeitos colaterais do que transformá-lo em classe. (você ainda não pode usar tal protocolo um com tipos de valor, mas declarando que uma classe não vai resolver que quer)
Arru
8

Atualização: parece que o manual foi atualizado e o exemplo ao qual eu estava me referindo foi removido. Veja a edição da resposta de @ flainez acima.

Original: usar @objc é a maneira certa de fazê-lo, mesmo que você não esteja interagindo com o Obj-C. Isso garante que seu protocolo esteja sendo aplicado a uma classe e não a uma enumeração ou estrutura. Consulte "Verificando a conformidade do protocolo" no manual.

William Rust
fonte
Como mencionado, a OMI não é uma resposta para a pergunta. Um programa Swift comum deve ser capaz de se manter independente de estar vinculado ao NS'ism (isso pode implicar não usar mais os delegados, mas alguma outra construção de design). Meu Swift MyClass puro, na verdade, não se importa se o destino é uma estrutura ou objeto, nem preciso de opcionais. Talvez eles consertem isso mais tarde, afinal, é um novo idioma. Possivelmente algo como 'protocolo de classe XYZ' se semântica de referência for necessária?
hnh 7/06/2014
4
Eu acho que também vale a pena notar que \ @objc tem efeitos colaterais adicionais - a sugestão do NSObjectProtocol de @eXhausted é um pouco melhor. Com \ @objc - se o delegado da classe usar um argumento de objeto, como 'handleResult (r: MySwiftResultClass)', o MySwiftResultClass agora precisará herdar do NSObject! E, presumivelmente, não é mais o namespace, etc. Em resumo: \ @objc é um recurso de ponte, não um idioma.
hnh 7/06/2014
Eu acho que eles resolveram isso. Agora você escreve: protocol MyClassDelegate: class {}
user3675131
Onde está a documentação sobre isso? Ou eu sou cego ou fazendo algo errado, porque eu não posso encontrar qualquer informação sobre isso ... O_O
BastiBen
Eu não tenho certeza se ele responde a pergunta do OP ou não, mas isso é útil especialmente se você está interoperar com ObjC-C;)
Dan Rosenstark
-1

O protocolo deve ser subclasse de AnyObject, classe

exemplo dado abaixo

    protocol NameOfProtocol: class {
   // member of protocol
    }
   class ClassName: UIViewController {
      weak var delegate: NameOfProtocol? 
    }
Rakesh Kunwar
fonte
-9

A Apple usa "NSObjectProtocol" em vez de "classe".

public protocol UIScrollViewDelegate : NSObjectProtocol {
   ...
}

Isso também funciona para mim e removeu os erros que eu estava vendo ao tentar implementar meu próprio padrão de delegado.

Michael Rose
fonte
5
Não relevante para a pergunta, esta pergunta é sobre a construção de uma classe Swift pura (especificamente sem NSObject) suportando um objeto delegado. Não se trata de implementar protocolos Objective-C, que é o que você está fazendo. O último requer @objc, também conhecido como NSObjectProtocol.
Hnh 19/04
OK, mas não recomendado.
precisa saber é o seguinte