Comment puis-je faire une référence de protocole faible dans Swift `` pur '' (sans @objc)

561

weakles références ne semblent pas fonctionner dans Swift sauf si a protocolest déclaré comme @objc, ce que je ne veux pas dans une application Swift pure.

Ce code donne une erreur de compilation ( weakne peut pas être appliqué à un type non-classe MyClassDelegate):

class MyClass {
  weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate {
}

J'ai besoin de préfixer le protocole avec @objc, alors ça marche.

Question: Quelle est la manière Swift «pure» d'accomplir un weak delegate?

hnh
la source

Réponses:

1039

Vous devez déclarer le type de protocole comme AnyObject.

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

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

En utilisant , AnyObjectvous dites que les classes peuvent seulement être conformes à ce protocole, alors que struct ou énumérations ne peut pas.

flainez
la source
25
Mon problème avec ces solutions est que l'appel du délégué provoque un crash - EXC_BAD_ACCESS (comme noté par d'autres ailleurs). Cela semble être un bug. La seule solution que j'ai trouvée est d'utiliser @objc et d'éliminer tous les types de données Swift du protocole.
Jim T
12
Quelle est la bonne façon de faire des délégués faibles maintenant dans Swift? La documentation Apple
n'affiche
2
Ce n'est pas toujours sûr - n'oubliez pas que vous ne devez affaiblir le délégué que s'il contient également une référence au délégant et que vous devez briser ce cycle de référence fort. Si le délégué ne contient aucune référence au délégant, le délégué peut sortir du cadre (car il est faible) et vous aurez des plantages et d'autres problèmes: / quelque chose à garder à l'esprit.
Trev14
5
BTW: Je pense que le "nouveau style" (Swift 5) est à faire protocol ProtocolNameDelegate: AnyObject, mais n'a pas d'importance.
hnh
1
Il devrait l'être AnyObjectcar classil sera déprécié à un moment donné.
José
283

Réponse supplémentaire

J'étais toujours confus quant à savoir si les délégués devaient être faibles ou non. Récemment, j'ai appris plus sur les délégués et quand utiliser des références faibles, alors permettez-moi d'ajouter quelques points supplémentaires ici pour le bien des futurs téléspectateurs.

  • Le but de l'utilisation du weakmot-clé est d'éviter les cycles de référence forts (conserver les cycles). Des cycles de référence forts se produisent lorsque deux instances de classe ont des références fortes l'une à l'autre. Leurs comptes de référence n'atteignent jamais zéro, ils ne sont donc jamais désalloués.

  • Vous devez utiliser uniquement weaksi le délégué est une classe. Les structures et énumérations rapides sont des types de valeur (leurs valeurs sont copiées lorsqu'une nouvelle instance est créée), pas des types de référence, de sorte qu'ils ne font pas de cycles de référence forts .

  • weakles références sont toujours facultatives (sinon vous les utiliseriez unowned) et utilisent toujours var(pas let) pour que l'option soit définie nillorsqu'elle est désallouée.

  • Une classe parent doit naturellement avoir une référence forte à ses classes enfants et donc ne pas utiliser le weakmot - clé. Cependant, lorsqu'un enfant veut une référence à son parent, il devrait en faire une référence faible en utilisant le weakmot - clé.

  • weakdoit être utilisé lorsque vous voulez une référence à une classe que vous ne possédez pas, pas seulement pour un enfant référençant son parent. Lorsque deux classes non hiérarchiques doivent se référencer, choisissez-en une pour être faible. Celui que vous choisissez dépend de la situation. Voir les réponses à cette question pour en savoir plus.

  • En règle générale, les délégués doivent être marqués commeweak car la plupart des délégués font référence à des classes qui ne leur appartiennent pas. Cela est tout à fait vrai lorsqu'un enfant utilise un délégué pour communiquer avec un parent. L'utilisation d'une référence faible pour le délégué est ce que la documentation recommande. (Mais voyez ça aussi.)

  • Les protocoles peuvent être utilisés à la fois pour les types de référence (classes) et les types de valeur (structures, énumérations). Donc, dans le cas probable où vous devez affaiblir un délégué, vous devez en faire un protocole uniquement objet. La façon de le faire consiste à ajouter AnyObjectà la liste d'héritage du protocole. (Dans le passé, vous faisiez cela en utilisant le classmot - clé, mais AnyObjectc'est préférable maintenant .)

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

Une étude plus approfondie

La lecture des articles suivants m'a aidé à mieux comprendre cela. Ils discutent également de problèmes connexes tels que le unownedmot - clé et les forts cycles de référence qui se produisent lors des fermetures.

en relation

Suragch
la source
5
Tout cela est agréable et intéressant, mais n'est pas vraiment lié à ma question initiale - qui ne concerne ni la faiblesse / ARC elle-même ni la raison pour laquelle les délégués sont généralement faibles. Nous savons déjà tout cela et nous nous sommes simplement demandé comment vous pouvez déclarer une référence de protocole faible (parfaitement répondu par @flainez).
hnh
30
Tu as raison. En fait, j'avais la même question que vous tout à l'heure, mais il me manquait beaucoup de ces informations de base. J'ai fait la lecture ci-dessus et pris les notes supplémentaires pour m'aider à comprendre tous les problèmes liés à votre question. Maintenant, je pense que je peux appliquer votre réponse acceptée et savoir pourquoi je le fais. J'espère que cela aidera aussi les futurs téléspectateurs.
Suragch
5
Mais puis-je avoir un protocole faible qui ne dépend PAS du type? Un protocole en soi ne se soucie pas de quel objet se conforme à lui-même. Ainsi, une classe ou une structure peuvent s'y conformer. Est-il possible d'avoir encore l'avantage de pouvoir s'y conformer, mais seulement les types de classe conformes sont-ils faibles?
FlowUI. SimpleUITesting.com
> parce que la plupart des délégués font référence à des classes qu'ils ne possèdent pas, je réécrirais ceci comme: la plupart des délégués. Sinon, l'objet non détenu devient le propriétaire
Victor Jalencas
36

AnyObject est la manière officielle d'utiliser une référence faible dans Swift.

class MyClass {
    weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate: AnyObject {
}

D'Apple:

Pour éviter des cycles de référence forts, les délégués doivent être déclarés comme références faibles. Pour plus d'informations sur les références faibles, consultez Cycles de référence forts entre les instances de classe. Marquer le protocole comme classe uniquement vous permettra plus tard de déclarer que le délégué doit utiliser une référence faible. Vous marquez un protocole comme étant uniquement de classe en héritant de AnyObject , comme indiqué dans Protocoles de classe uniquement.

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

Tim Chen
la source
7
Intéressant. Est classdéconseillé dans Swift 4.1?
hnh
@hnh Vous pouvez toujours créer un "pseudo-protocole" en en faisant une classe, mais protocole: AnyObject fait exactement ce que l'OP demande avec moins d'effets secondaires que d'en faire une classe. (vous ne pouvez toujours pas utiliser un tel protocole avec des types de valeur, mais le déclarer une classe ne résoudra pas cela non plus)
Arru
8

Mise à jour: Il semble que le manuel a été mis à jour et l'exemple auquel je faisais référence a été supprimé. Voir la modification de la réponse de @ flainez ci-dessus.

Original: Utiliser @objc est la bonne façon de le faire même si vous n'interopérez pas avec Obj-C. Il garantit que votre protocole est appliqué à une classe et non à une énumération ou une structure. Voir "Vérification de la conformité du protocole" dans le manuel.

William Rust
la source
Comme mentionné, l'OMI n'est pas une réponse à la question. Un programme Swift simple devrait pouvoir se débrouiller seul sans être lié au NS'isme (cela pourrait impliquer de ne plus utiliser de délégué mais une autre construction de conception). Ma pure Swift MyClass ne se soucie pas vraiment de savoir si la destination est une structure ou un objet, ni besoin d'options. Peut-être qu'ils pourront le réparer plus tard, c'est une nouvelle langue après tout. Peut-être quelque chose comme «protocole de classe XYZ» si la sémantique de référence est requise?
hnh
4
Je pense qu'il vaut également la peine de noter que \ @objc a des effets secondaires supplémentaires - la suggestion NSObjectProtocol de @eXhausted est un peu meilleure. Avec \ @objc - si le délégué de classe prend un argument d'objet, comme 'handleResult (r: MySwiftResultClass)', MySwiftResultClass doit maintenant hériter de NSObject! Et sans doute ce n'est plus l'espace de noms non plus, etc. En bref: \ @objc est une fonction de pontage, pas une langue.
hnh
Je pense qu'ils ont résolu ce problème. Vous écrivez maintenant: protocole MyClassDelegate: class {}
user3675131
Où est la documentation à ce sujet? Soit je suis aveugle, soit je fais quelque chose de mal, car je ne trouve aucune information à ce sujet ... O_O
BastiBen
Je ne sais pas si cela répond à la question du PO ou non, mais cela est utile surtout si vous interopérez avec Objc-C;)
Dan Rosenstark
-1

le protocole doit être une sous-classe de AnyObject, classe

exemple donné ci-dessous

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

Apple utilise "NSObjectProtocol" au lieu de "classe".

public protocol UIScrollViewDelegate : NSObjectProtocol {
   ...
}

Cela fonctionne également pour moi et a supprimé les erreurs que je voyais lorsque j'essayais d'implémenter mon propre modèle de délégué.

Michael Rose
la source
5
Pas pertinent pour la question, cette question concerne la construction d'une classe Swift pure (en particulier pas NSObject) prenant en charge un objet délégué. Il ne s'agit pas d'implémenter des protocoles Objective-C, c'est ce que vous faites. Ce dernier nécessite @objc aka NSObjectProtocol.
hnh
OK, mais pas recommandé.
DawnSong