Мой ответ состоит из двух частей. В первой части я хотел бы обсудить ваше дизайнерское решение, а во втором - еще одно альтернативное решение с использованием магии Obj-C.
Конструктивные соображения
Похоже, что вы хотите ClassB
, чтобы не быть в состоянии переопределить реализацию по умолчанию.
Прежде всего, в таком случае вы, вероятно, следует также применять
optional public func numberOfSections(in tableView: UITableView) -> Int
в вашем ClassA
для последовательности или ClassB
будет иметь возможность вернуть что-то еще там без возможности возврата дополнительных ячеек.
На самом деле это неподобающее поведение - это то, что мне не нравится в таком дизайне. Что делать, если пользователь вашей библиотеки хочет добавить больше разделов и ячеек к тому же UITableView
? В этом аспекте дизайн, описанный Sulthan с ClassA
, обеспечивающий реализацию по умолчанию и ClassB
, обертывает его для делегирования и, вероятно, иногда меняет значения по умолчанию, представляется мне предпочтительным. Я имею в виду что-то вроде
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (section == 0) {
return libTableDataSource.tableView(tableView: tableView, numberOfRowsInSection: section)
}
else {
// custom logic for additional sections
}
}
Также такая конструкция имеет еще одно преимущество, не нуждаясь продвинутые трюки Obj-C для работы в более сложных сценариев, таких как UITableViewDelegate
, потому что вы не должны реализовать дополнительные методы, которые вы не хотите в либо ClassA
, либо ClassB
, и вы можете добавить методы, которые вам (пользователю библиотеки) нужны в ClassB
.
Obj-C магия
Предположим, что вы все еще хотите, чтобы ваше поведение по умолчанию, чтобы стоять как единственно возможный выбор для методов вы реализованы, но давайте настроить другие методы. Предположим также, что мы имеем дело с чем-то вроде UITableView
, который разработан с использованием метода Obj-C, т. Е. В значительной степени полагается на необязательные методы в делегатах и не предоставляет простого способа назвать стандартное поведение Apple (это неверно для UITableViewDataSource
, но верно для UITableViewDelegate
потому что, кто знает, как реализовать что-то вроде
optional public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
в назад и вперед совместимом пути, чтобы соответствовать стилю Apple, по умолчанию на каждой прошивке).
Так в чем же решение? Используя немного магии Obj-C, мы можем создать наш класс, который будет иметь наши реализации по умолчанию для методов протокола, которые мы хотим, чтобы, если мы предоставим ему еще один делегат с несколькими дополнительными методами, наш объект будет выглядеть так, как будто он имеет их слишком.
Попытка # 1 (NSProxy)
Сначала мы начнем с общим SOMulticastProxy
, который вроде прокси, который делегаты призывает к двум объектам (см источников хелперов SOOptionallyRetainHolder далее).
SOMulticastProxy.ч
@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
в основном следующий образом: найти первый делегат, который отвечает требуемому селектор и прямому вызов там. Если ни один из делегатов не знает селектора, скажите, что мы этого не знаем. Это более мощная, чем просто автоматизация делегирования всех методов, потому что SOMulticastProxy
эффективно объединяет необязательные методы из обоих переданных объектов без необходимости предоставлять где-то по умолчанию реализации для каждого из них (необязательные методы).
Обратите внимание, что это можно сделать в соответствии с несколькими протоколами (UITableViewDelegate
+ UITableViewDataSource
), но я не беспокоил.
Теперь с помощью этой магии мы можем просто присоединиться к двум классам, которые реализуют протокол UITableViewDataSource
и получают желаемый объект. Но я думаю, что имеет смысл создать более явный протокол для второго делегата, чтобы показать, что некоторые методы не будут перенаправлены в любом случае.
@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
}
Теперь мы можем иметь наш LibTableDataSource
, как
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
}
}
Предполагая externalTableDataSource
является объект класса пользователя библиотеки, которая реализует MyTableDataSource
протокол, использование просто
let wrappedTableDataSource: UITableViewDataSource = LibTableDataSource.wrap(externalTableDataSource)
Вот источник для SOOptionallyRetainHolder вспомогательный класс. SOOptionallyRetainHolder - это класс, который позволяет вам контролировать, что объект будет сохранен или нет. Это полезно, потому что NSArray
по умолчанию сохраняет свои объекты и в типичном сценарии использования вы хотите сохранить первый делегат и не сохраняют вторую (спасибо Джузеппе Ланца за упоминание этого аспекта, что я совершенно забыл о первоначально)
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
Попытка # 2 (наследование от класса Obj-C)
Если иметь опасные SOMulticastProxy
в вашем коде выглядит немного как излишним, вы можете создать более специализированный базовый класс 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
А затем сделайте свой LibTableDataSource
почти таким же, как в попытке # 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
}
}
, а использование абсолютно идентично используемому с попыткой № 1.Также это решение еще проще реализовать два протокола (UITableViewDelegate
+ UITableViewDataSource
).
Немного больше мощности Obj-C волшебной
На самом деле вы можете использовать Obj-C магию, чтобы сделать MyTableDataSource
протокол отличается от UITableDataSource
в именах методов, а не копировать-вставить их и даже изменять такие параметры, как не передавая UITableView
вообще или передавая свой пользовательский объект вместо UITableView
. Я сделал это один раз, и это сработало, но я не рекомендую это делать, если у вас нет веских оснований для этого.
Легко, либо 'classA' перенаправляет некоторые вызовы на' classB', либо вы создадите 'classC', который будет делегатом, и перенаправит либо на' A', и на 'B'. – Sulthan
@ Султан я вижу. Я хочу сделать все доступные методы 'UITableViewDataSource'' classB', и было бы неплохо избежать написания всех методов один за другим, чтобы перенаправить. Итак, из того, что я понимаю, вариант 1 не будет работать для меня. И то же самое для варианта 2, так как мне придется писать много кода. Есть что-то, чего я не хватает? (возможно, есть простой способ перенаправить то, что мне не хватает). Также отредактировал вопрос с еще одной информацией. –
Если ClassA существует исключительно для целей внедрения значений по умолчанию, вы можете позаботиться об этом быстрее, используя расширение протокола. https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID521 –