2017-02-10 2 views
5

Je ne suis pas sûr que cela puisse être fait, ou même s'il n'est pas recommandé.Implémentation du protocole UITableViewDataSource dans deux classes différentes

Ce que je suis en train de réaliser est le suivant:

J'ai un 2 classes classA et classB qui ont une référence au même UITableview exemple. Ce que je veux pour classA de prendre soin de la mise en œuvre des 2 méthodes nécessaires du protocole UITableViewDataSource:

  • numberOfRowsInSection
  • cellForRowAt

Je veux classB être en mesure de mettre en œuvre l'autre des méthodes optionnelles comme titleForHeaderInSection par exemple.

Alors, comment classA peut-il avoir une implémentation par défaut de certaines méthodes de protocole, et laisser classB être une classe qui peut être construite en plus de ce que classB a fait? En un sens, le problème auquel je suis confronté est le suivant: Comment plusieurs classes peuvent être la source de données d'un seul UITableView?

EDIT: classA sera dans une bibliothèque que je suis en train d'écrire qui prend en charge la construction des parties centrales du tableView. classB sera utilisé par le développeur tiers pour personnaliser principalement son apparence.

+1

facilement, que ce soit 'classA' va rediriger certains appels vers' classB' ou vous créerez 'classC' qui sera le délégué et redirigera soit' A' et 'B'. – Sulthan

+0

@Sulthan je vois. Je veux rendre toutes les méthodes 'UITableViewDataSource' disponibles à' classB', et il serait bon d'éviter d'écrire toutes les méthodes une par une afin de les rediriger. Donc, d'après ce que je comprends, l'option 1 ne fonctionnerait pas pour moi. Et même chose pour l'option 2 puisque je devrai écrire beaucoup de code. Y a-t-il quelque chose qui me manque? (peut-être y a-t-il un moyen facile de rediriger ce qui me manque). Également édité la question avec une info de plus. –

+0

Si ClassA existe uniquement dans le but d'implémenter des valeurs par défaut, vous pouvez en prendre soin dans swift avec une extension de protocole à la place. https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID521 –

Répondre

3

Je pense que la seule solution sans rediriger manuellement tout est d'utiliser l'implémentation par défaut des méthodes de protocole, par ex. :

protocol MyTableViewProtocol : UITableViewDelegate, UITableViewDataSource { 

} 

extension MyTableViewProtocol { 
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
     return 5 
    } 
} 

Et puis faire ClassB à mettre en œuvre MyTableViewProtocol au lieu de UITableViewDelegate et UITableViewDataSource.

Toutefois, une telle solution ne fonctionnera pas car les extensions de protocole ne sont pas accessibles par Obj-C.

Je pense qu'une solution plus propre (et de travail) serait de créer la mise en œuvre de numberOfRowsInSection et cellForRowAt en dehors du protocole et laisser ClassB de les appeler dans la méthode déléguée, par exemple:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
    return MyTable.tableView(tableView: tableView, numberOfRowsInSection: section) 
} 

Une telle solution sera plus clair pour l'utilisateur car il contiendra moins de "magie".

Bien sûr, la solution classique est de définir votre propre délégué:

protocol MyTableViewProtocol { 
    func myTableView(_ tableView: MyTableView, ...) 
    ...  
} 

et tout rediriger à partir de votre délégué.

Cette solution rend impossible pour ClassB d'écraser la fonction déléguée que vous ne voulez pas qu'elle écrase.

+0

Grande réponse, en particulier la partie «plus claire pour l'utilisateur». Je pense que ça va être celui que j'utiliserai. Va voir s'il y a plus de suggestions. Cependant, je suis curieux de savoir si vous suggérez d'utiliser la solution ci-dessus, ou rediriger manuellement un? (Parce que je suis allé avec redirection manuellement maintenant, et il fonctionne très bien. Mais inconvénient est qu'il ya beaucoup de code de passe-partout, et un peu de magie qui se passe derrière le dos de l'utilisateur) –

+1

@GuyDaher Je ne pense pas redirigeant est un idéal solution mais je l'ai utilisé plusieurs fois surtout dans des situations où j'ai aussi une vue de table sous-classée donc je peux créer un meilleur délégué qui utilise déjà mon type sous-classé. Cependant, c'était toujours pour un usage interne dans un projet, pas pour une bibliothèque ouverte. – Sulthan

0

Vous pouvez faire des choses comme ça.Les personnes qui écrivent class B utilise extension A pour ajouter des fonctions UITableViewDataSource.

// file A.swift 
class A:NSObject, UITableViewDataSource { 
    var b:B! = nil 

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
     return 0 
    } 

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
     let cell = UITableViewCell() 
     return cell 
    } 
} 

protocol SectionNameProtocol { 
    var sectionName:[String] { get set } 
} 

// file B.swift 

class B:SectionNameProtocol { 
    unowned var a:A 
    var sectionName: [String] = [] 

    init(a:A) { 
     self.a = a 
     a.b = self 
    } 
} 

extension A { 
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 
     return b.sectionName[section] 
    } 
} 
+1

C'est une bonne réponse, merci! ma préférence est la réponse de @Sulthan car elle correspond plus à l'architecture que je veux. –

+0

La réutilisabilité de cette solution est cependant très limitée. Et si vous avez besoin de plusieurs implémentations? – Sulthan

+0

@Sulthan Je n'ai pas vu qu'il y avait plusieurs exigences d'implémentations dans la question. Ce serait donc un plus, mais pas une exigence. De plus, ma réponse peut limiter la classe B de l'implémentation de fonctions comme 'func tableView (_tableView: UITableView, section numberOfRowsInSection: Int)'. Mais votre réponse d'extension de protocole ne peut pas. Votre réponse repose sur les personnes qui utilisent la classe B pour suivre les règles, ce qui n'est pas une très bonne idée que la mienne. –

3

Ma réponse se compose de deux parties. Dans la première partie, j'aimerais discuter de votre décision de conception et, en deuxième lieu, fournir une autre solution alternative en utilisant la magie Obj-C.

Considérations de conception

On dirait que vous voulez ClassB de ne pas être en mesure de remplacer votre implémentation par défaut.

tout d'abord, dans ce cas, vous avez probablement devrait également mettre en œuvre

optional public func numberOfSections(in tableView: UITableView) -> Int 

dans votre ClassA de cohérence ou ClassB sera en mesure de retourner quelque chose d'autre, sans possibilité de retourner des cellules supplémentaires.

En fait, ce comportement prohibitif est ce que je n'aime pas dans une telle conception. Que faire si l'utilisateur de votre bibliothèque veut ajouter plus de sections et de cellules à la même UITableView? Dans cette conception d'aspect tel que décrit par Sulthan avec ClassA fournissant l'implémentation par défaut et ClassB l'enveloppant pour déléguer et probablement parfois changer les valeurs par défaut me semble préférable. Je veux dire quelque chose comme

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
    if (section == 0) { 
     return libTableDataSource.tableView(tableView: tableView, numberOfRowsInSection: section) 
    } 
    else { 
     // custom logic for additional sections 
    } 
} 

également cette conception a un autre avantage de ne pas avoir besoin des astuces Obj-C avancés pour travailler dans des scénarios plus complexes tels que UITableViewDelegate parce que vous n'avez pas à mettre en œuvre des méthodes optionnelles que vous ne voulez pas soit ClassA ou ClassB et peut toujours ajouter des méthodes dont vous (l'utilisateur de la bibliothèque) avez besoin dans ClassB.

magie Obj-C

Supposons que vous ne voulez toujours faire votre comportement par défaut de se présenter comme le seul choix possible pour les méthodes que vous avez mis en place, mais d'autres méthodes permettent de personnaliser. Supposons également que nous traitons quelque chose comme UITableView qui est conçu en mode Obj-C, c'est-à-dire qui repose lourdement sur des méthodes optionnelles dans les délégués et ne fournit aucun moyen simple d'appeler le comportement standard d'Apple (ce n'est pas vrai pour UITableViewDataSource mais true pour UITableViewDelegate car qui sait comment mettre en œuvre quelque chose comme

optional public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat 

de manière compatibilité ascendante et descendante pour correspondre au style de défaut Apple sur tous les iOS).

Alors, quelle est la solution? En utilisant un peu de magie Obj-C, nous pouvons créer notre classe, qui aura nos implémentations par défaut pour les méthodes de protocole que nous voulons telles que si nous lui fournissons un autre délégué qui a d'autres méthodes optionnelles implémentées, notre objet semblera les avoir aussi.

Tentative n ° 1 (NSProxy)

Tout d'abord, nous commençons par un SOMulticastProxy générique qui est une sorte de procuration que les délégués appelle à deux objets (voir sources d'aide SOOptionallyRetainHolder plus).

SOMulticastProxy.h

@interface SOMulticastProxy : NSProxy 

+ (id)proxyForProtocol:(Protocol *)targetProtocol firstDelegateR:(id <NSObject>)firstDelegate secondDelegateNR:(id <NSObject>)secondDelegate; 

// This provides sensible defaults for retaining: typically firstDelegate will be created in 
// place and thus should be retained while the second delegate most probably will be something 
// like UIViewController and retaining it will retaining it will lead to memory leaks 
+ (id)proxyForProtocol:(Protocol *)targetProtocol firstDelegate:(id <NSObject>)firstDelegate retainFirst:(BOOL)retainFirst 
     secondDelegate:(id <NSObject>)secondDelegate retainSecond:(BOOL)retainSecond; 
@end 

SOMulticastProxy.m

@interface SOMulticastProxy() 
@property(nonatomic) Protocol *targetProtocol; 
@property(nonatomic) NSArray<SOOptionallyRetainHolder *> *delegates; 

@end 

@implementation SOMulticastProxy { 
} 

- (id)initWithProtocol:(Protocol *)targetProtocol firstDelegate:(id <NSObject>)firstDelegate retainFirst:(BOOL)retainFirst 
     secondDelegate:(id <NSObject>)secondDelegate retainSecond:(BOOL)retainSecond { 
    self.targetProtocol = targetProtocol; 
    self.delegates = @[[SOOptionallyRetainHolder holderWithTarget:firstDelegate retainTarget:retainFirst], 
      [SOOptionallyRetainHolder holderWithTarget:secondDelegate retainTarget:retainSecond]]; 
    return self; 
} 

+ (id)proxyForProtocol:(Protocol *)targetProtocol firstDelegate:(id <NSObject>)firstDelegate retainFirst:(BOOL)retainFirst 
     secondDelegate:(id <NSObject>)secondDelegate retainSecond:(BOOL)retainSecond { 
    return [[self alloc] initWithProtocol:targetProtocol 
          firstDelegate:firstDelegate 
           retainFirst:retainFirst 
          secondDelegate:secondDelegate 
          retainSecond:retainSecond]; 

} 


+ (id)proxyForProtocol:(Protocol *)targetProtocol firstDelegateR:(id <NSObject>)firstDelegate secondDelegateNR:(id <NSObject>)secondDelegate { 
    return [self proxyForProtocol:targetProtocol firstDelegate:firstDelegate retainFirst:YES 
        secondDelegate:secondDelegate retainSecond:NO]; 
} 

- (BOOL)conformsToProtocol:(Protocol *)aProtocol { 
    if (self.targetProtocol == aProtocol) 
     return YES; 
    else 
     return NO; 
} 

- (NSObject *)findTargetForSelector:(SEL)aSelector { 
    for (SOOptionallyRetainHolder *holder in self.delegates) { 
     NSObject *del = holder.target; 
     if ([del respondsToSelector:aSelector]) 
      return del; 
    } 
    return nil; 
} 

- (BOOL)respondsToSelector:(SEL)aSelector { 

    BOOL superRes = [super respondsToSelector:aSelector]; 
    if (superRes) 
     return superRes; 

    NSObject *delegate = [self findTargetForSelector:aSelector]; 

    return (delegate != nil); 
} 

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { 
    NSObject *delegate = [self findTargetForSelector:sel]; 
    if (delegate != nil) 
     return [delegate methodSignatureForSelector:sel]; 
    else 
     return nil; 
} 

- (void)forwardInvocation:(NSInvocation *)invocation { 
    NSObject *delegate = [self findTargetForSelector:invocation.selector]; 
    if (delegate != nil) 
     [invocation invokeWithTarget:delegate]; 
    else 
     [super forwardInvocation:invocation]; // which will effectively be [self doesNotRecognizeSelector:invocation.selector]; 
} 

@end 

SOMulticastProxy est essentiellement suivant: trouver premier délégué qui répond au sélecteur nécessaire et il le renvoi d'appel. Si aucun des délégués ne connaît le sélecteur - disons que nous ne le savons pas. Ceci est plus puissant que la simple automatisation de la délégation de toutes les méthodes car SOMulticastProxy fusionne efficacement les méthodes optionnelles des deux objets passés sans qu'il soit nécessaire de fournir quelque part des implémentations par défaut pour chacune d'entre elles (méthodes facultatives).

Notez qu'il est possible de le rendre conforme à plusieurs protocoles (UITableViewDelegate + UITableViewDataSource) mais je n'ai pas dérangé.

Maintenant, avec cette magie, nous pouvons simplement joindre deux classes qui implémentent à la fois le protocole UITableViewDataSource et obtiennent un objet que vous voulez. Mais je pense qu'il est logique de créer un protocole plus explicite pour le second délégué afin de montrer que certaines méthodes ne seront pas transmises de toute façon.

@objc public protocol MyTableDataSource: NSObjectProtocol { 


    @objc optional func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? 

    // copy here all the methods except the ones you've implemented 

} 

Maintenant, nous pouvons avoir notre LibTableDataSource comme

class LibTableDataSource: NSObject, UIKit.UITableViewDataSource { 

    class func wrap(_ dataSource: MyTableDataSource) -> UITableViewDataSource { 
     let this = LibTableDataSource() 
     return SOMulticastProxy.proxy(for: UITableViewDataSource.self, firstDelegateR: this, secondDelegateNR: dataSource) as! UITableViewDataSource 
    } 

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
     return your logic here 
    } 

    func numberOfSections(in tableView: UITableView) -> Int { 
     return your logic here 
    } 

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
     return your logic here 
    } 
} 

En supposant externalTableDataSource est un objet de la classe de l'utilisateur de la bibliothèque qui implémente le protocole MyTableDataSource, l'utilisation est tout simplement

let wrappedTableDataSource: UITableViewDataSource = LibTableDataSource.wrap(externalTableDataSource) 

est ici la source pour SOOptionallyRetainHolder classe d'assistance. SOOptionallyRetainHolder est une classe qui vous permet de contrôler si l'objet sera conservé ou non. Cela est utile car NSArray par défaut conserve ses objets et dans le scénario typique d'utilisation que vous souhaitez conserver premier délégué et ne conserve pas la seconde (merci Giuseppe Lanza pour mentionner cet aspect que je suis totalement oublié au départ)

SOOptionallyRetainHolder.h

@interface SOOptionallyRetainHolder : NSObject 
@property(nonatomic, readonly) id <NSObject> target; 

+ (instancetype)holderWithTarget:(id <NSObject>)target retainTarget:(BOOL)retainTarget; 
@end 

SOOptionallyRetainHolder.m

@interface SOOptionallyRetainHolder() 
@property(nonatomic, readwrite) NSValue *targetNonRetained; 
@property(nonatomic, readwrite) id <NSObject> targetRetained; 
@end 

@implementation SOOptionallyRetainHolder { 
@private 

} 

- (id)initWithTarget:(id <NSObject>)target retainTarget:(BOOL)retainTarget { 
    if (!(self = [super init])) return self; 
    if (retainTarget) 
     self.targetRetained = target; 
    else 
     self.targetNonRetained = [NSValue valueWithNonretainedObject:target]; 

    return self; 
} 

+ (instancetype)holderWithTarget:(id <NSObject>)target retainTarget:(BOOL)retainTarget { 
    return [[self alloc] initWithTarget:target retainTarget:retainTarget]; 
} 

- (id <NSObject>)target { 

    return self.targetNonRetained != nil ? self.targetNonRetained.nonretainedObjectValue : self.targetRetained; 
} 

@end 

Tentative n ° 2 (héritage de la classe Obj-C)

Si avoir SOMulticastProxy dangereux dans votre codebase ressemble un peu à un surpuissant, vous pouvez créer des bases plus spécialisées classe SOTotallyInternalDelegatingBaseLibDataSource:

SOTotallyInternalDelegatingBaseLibDataSource.h

@interface SOTotallyInternalDelegatingBaseLibDataSource : NSObject <UITableViewDataSource> 
- (instancetype)initWithDelegate:(NSObject *)delegate; 

@end 

SOTotallyInternalDelegatingBaseLibDataSource.m

#import "SOTotallyInternalDelegatingBaseLibDataSource.h" 


@interface SOTotallyInternalDelegatingBaseLibDataSource() 
@property(nonatomic) NSObject *delegate; 

@end 

@implementation SOTotallyInternalDelegatingBaseLibDataSource { 

} 

- (instancetype)initWithDelegate:(NSObject *)delegate { 
    if (!(self = [super init])) return self; 

    self.delegate = delegate; 

    return self; 
} 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 
    [self doesNotRecognizeSelector:_cmd]; 
    return 0; 
} 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    [self doesNotRecognizeSelector:_cmd]; 
    return nil; 
} 


#pragma mark - 

- (BOOL)respondsToSelector:(SEL)aSelector { 

    BOOL superRes = [super respondsToSelector:aSelector]; 
    if (superRes) 
     return superRes; 


    return [self.delegate respondsToSelector:aSelector]; 
} 

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { 
    NSMethodSignature *superRes = [super methodSignatureForSelector:sel]; 
    if (superRes != nil) 
     return superRes; 

    return [self.delegate methodSignatureForSelector:sel]; 

} 

- (void)forwardInvocation:(NSInvocation *)invocation { 
    [invocation invokeWithTarget:self.delegate]; 
} 

@end 

Et puis faites votre LibTableDataSource presque le même que dans Attempt # 1

class LibTableDataSource: SOTotallyInternalDelegatingBaseLibDataSource { 

    class func wrap(_ dataSource: MyTableDataSource) -> UITableViewDataSource { 
     return LibTableDataSource2(delegate: dataSource as! NSObject) 
    } 

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
     return your logic here 
    } 

    func numberOfSections(in tableView: UITableView) -> Int { 
     return your logic here 
    } 

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
     return your logic here 
    } 
} 

et l'utilisation est tout à fait identique à celui avec tentative # 1.Aussi cette solution est encore plus facile à mettre en œuvre deux protocoles (UITableViewDelegate + UITableViewDataSource) en même temps.

Un peu plus sur la puissance de la magie Obj-C

En fait, vous pouvez utiliser la magie Obj-C pour faire MyTableDataSource protocole différent de UITableDataSource dans les noms de méthode plutôt que de les copier-coller et même modifier les paramètres tels que ne pas passer UITableView ou passer votre objet personnalisé au lieu de UITableView. Je l'ai fait une fois et ça a marché mais je ne le recommande pas à moins d'avoir une très bonne raison de le faire.

+0

Merci pour la réponse. Des trucs intéressants sur Obj-C que je ne connaissais pas depuis que je suis un dev devift –

+1

array array va conserver les objets délégués. Ce n'est pas une bonne solution imho. Mais je ne vais pas le voter. Cela reste une approche intéressante. –

+0

@GiuseppeLanza, merci de me le rappeler. Je n'ai pas écrit de code Obj-C depuis un certain temps et je l'ai juste oublié. Voir ma mise à jour qui utilise la classe d'aide 'SOOptionallyRetainHolder' qui résout ce problème. @GuyDahher, vous pouvez également jeter un oeil sur le code mis à jour qui évite les fuites de mémoire. – SergGr

0

Je pense que la meilleure façon de le faire est de sous-classer UIViewController dans votre ClassA et d'implémenter UITableViewDataSource. Pour empêcher l'appel des méthodes requises implémentées dans ClassA, mettez simplement le mot clé final dans l'implémentation .

Voici ma solution:

ClassA

import UIKit 

class ClassA: UIViewController, UITableViewDataSource { 

    // MARK: - Table view data source 

    final func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
     return 10 
    } 

    final func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 
     let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) 

     cell.textLabel?.text = "Cell \(indexPath.row) in section \(indexPath.section)" 

     return cell 
    } 
} 

ClassB

import UIKit 

class ClassB: ClassA { 

    @IBOutlet weak var tableView: UITableView! 

    override func viewDidLoad() { 
     tableView.dataSource = self 
    } 

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 
     return "Header \(section)" 
    } 

    func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { 
     return "Footer \(section)" 
    } 
} 

Voici ce que vous obtenez:

enter image description here

+0

Que faire si la classe d'implémentation ne sera pas un 'UIViewController'? Et si ce ne serait qu'une vue? Ou si ce sera un autre type de contrôleur, par ex. 'UITableViewController'? – Sulthan

+0

D'accord avec Sulthan –

+0

Si vous voulez un comportement personnalisé, vous devez créer une bibliothèque personnalisée. Vous devez donc sous-classer tous les objets que vous souhaitez implémenter ce comportement. À mon avis, il n'y a pas d'autre moyen de le faire. –

-1

Comme quelqu'un d'autre l'a mentionné, nous avons besoin d'un proxy. Pour concevoir un proxy qui sera sûr en termes de cycles de retenue, c'est assez facile. Pour le rendre générique et flexible, c'est une histoire totalement différente. Tout le monde ici sait que le modèle de délégué nécessite que l'objet délégué soit faible pour éviter les cycles de retenue (un conservateur b et b retient un afin que personne ne soit désalloué).

La solution immédiate est d'avoir des variables de N dans votre proxy qui sont faibles bien sûr pour que vous puissiez transmettre à ces objets le délégué appelle

class MyProxy: NSObject, UITableViewDelegate { 
    weak var delegate1: UITableViewDelegate? 
    weak var delegate2: UITableViewDelegate? 

    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 
     delegate1?.tableView?(tableView, didSelectRowAt: indexPath) 
     delegate2?.tableView?(tableView, didSelectRowAt: indexPath) 
    } 
} 

Bien sûr, cela fonctionnera. Mais ce n'est pas flexible du tout. Vous pouvez avoir seulement 2 délégués, et clairement si vous en voulez plus, vous devez ajouter delegate3 var, n'oubliez pas de mettre à jour toutes vos méthodes et ainsi de suite. Quelqu'un pourrait penser "Bien, ayons un tableau de délégués" ... Faux. Le tableau retiendrait les délégués qui ne seront plus faibles et nous aurons un cycle de conservation.

La solution

Pour faire avancer les choses flexibles que j'ai créé une collection faible. Ce code vous permettra d'avoir une collection d'éléments faibles en utilisant des génériques. Vous serez en mesure d'implémenter autant de proxy que vous le souhaitez et ces proxy peuvent contenir autant de délégués que vous préférez.

public struct WeakContainer<T: NSObjectProtocol> { 
    public weak var delegate: T? 
} 

public struct WeakCollection<T: NSObjectProtocol> { 
    private var delegates: [WeakContainer<T>] = [WeakContainer<T>]() 

    public init(){} 

    public init(with delegate: T) { 
     add(object: delegate) 
    } 

    public mutating func add(object: T) { 
     let container = WeakContainer(delegate: object) 
     delegates.append(container) 
    } 

    public mutating func remove(object: T) { 
     guard let index = delegates.index(where: { 
      return object.isEqual($0.delegate) 
     }) else { return } 
     delegates.remove(at: index) 
    } 

    public mutating func execute(_ closure: ((_ object: T) throws -> Void)) rethrows { 
     let localDelegates = delegates 
     try localDelegates.forEach { (weakContainer) in 
      guard let delegate = weakContainer.delegate else { 
       cleanup() 
       return 
      } 
      try closure(delegate) 
     } 
    } 

    private mutating func cleanup() { 
     delegates.sort { (a, b) -> Bool in 
      return a.delegate == nil 
     } 
     while let first = delegates.first, first.delegate == nil { 
      delegates.removeFirst() 
     } 
    } 
} 

Cela vous permettra de faire quelque chose comme ceci:

public class TableViewDelegateProxy: NSObject, UITableViewDelegate { 
    var delegates = WeakCollection<UITableViewDelegate>() 

    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 
     delegates.execute { (delegate) in 
      delegate.tableView?(tableView, didSelectRowAt: indexPath) 
     } 
    } 
} 

Comme vous pouvez le voir, ces quelques lignes seront en sécurité, comme weakCollection enregistre une référence faible aux délégués, il sera se nettoyer quand des délégués ont été trouvés et qu'il peut contenir des objets d'un protocole pour être super flexible et se plier à vos besoins.

+0

Bien que ce soit correct, votre réponse ne répond pas réellement * cette * question. C'est un commentaire pour une autre réponse et ne devrait pas être ici du tout. Votre code appartiendrait à http://stackoverflow.com/questions/24127587/how-do-i-declare-an-array-of-weak-references-in-swift. Cependant, notez qu'il y a quelques problèmes. Votre 'nettoyage 'est une façon très compliquée d'implémenter' .filter'. Votre 'remove' devrait probablement utiliser' === 'et non l'égalité. Votre 'nettoyage 'peut être appelé plusieurs fois pendant un' execute'. – Sulthan

+0

Je ne suis pas d'accord. Bien qu'il soit vrai qu'il y ait une optimisation à ce code, je crois que cette réponse répond à la question. J'ai expliqué comment construire un proxy qui peut contenir n délégués en toute sécurité, et pas seulement deux. Je suis désolé que vous ne soyez pas d'accord, mais je ne pense pas que je devrais mériter un downvote. –

+0

Je n'ai pas downvote Je considérais un drapeau :) Je soulignais seulement que vous ne répondez pas à la question de l'OP. Vous ajoutez une classe d'utilitaires qui pourrait être utilisée dans le cadre d'une solution, mais vous ne donnez pas la solution vous-même. – Sulthan