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.
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
@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. –
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 –