在两个不同的类

时间:2017-02-10 16:38:56

标签: ios swift uitableview protocols swift-protocols

我不确定是否可以这样做,或者甚至不推荐。

我想要实现的目标如下:

我有两个类classAclassB,它们引用了同一个UITableview实例。我想要的是classA负责UITableViewDataSource协议所需的2种方法的实现:

  • numberOfRowsInSection
  • cellForRowAt

然后我希望classB能够实现其他可选方法,例如titleForHeaderInSection

那么classA如何能够默认实现某些协议方法,让classB成为可以在classB之上构建的类?

在某种程度上,我面临的问题如下:多个类如何成为单个UITableView的数据源?

修改 classA将出现在我正在编写的库中,该库负责构建tableView的核心部分。第三方开发人员将使用classB来主要自定义其外观。

5 个答案:

答案 0 :(得分:3)

我认为没有手动重定向所有内容的唯一解决方案是使用协议方法的默认实现,例如:

protocol MyTableViewProtocol : UITableViewDelegate, UITableViewDataSource {

}

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

然后让ClassB实施MyTableViewProtocol而不是UITableViewDelegateUITableViewDataSource

但是,这样的解决方案将无效,因为Obj-C无法访问协议扩展。

我认为一个更清洁(和工作)的解决方案是在协议之外创建numberOfRowsInSectioncellForRowAt的实现,让ClassB在委托方法中调用它们,例如:

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

这样的解决方案对于用户来说将更加清晰,因为它将包含更少的“魔力”。

当然,经典的解决方案是定义自己的委托:

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

并从您的代表处重定向所有内容。

此解决方案使ClassB无法覆盖您不希望覆盖的委托函数。

答案 1 :(得分:3)

我的回答包括两部分。在第一部分中,我想讨论您的设计决策,然后再使用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等更复杂的场景中使用,因为你不必实现你不想要的可选方法。 ClassAClassB之一,仍然可以将您(图书馆用户)所需的方法添加到ClassB

Obj-C magic

假设您仍然希望将默认行为作为您已实施的方法的唯一可能选择,但让我们自定义其他方法。还假设我们正在处理像UITableView这样的东西,它是以Obj-C的方式设计的,即严重依赖代表中的可选方法,并没有提供任何简单的方法来调用Apple的标准行为(UITableViewDataSource不是这样,但UITableViewDelegate也是如此,因为谁知道如何实现

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

以向后和向前兼容的方式匹配每个iOS上的默认Apple风格。

那么解决方案是什么?使用一点Obj-C魔术,我们可以创建我们的类,它将具有我们想要的协议方法的默认实现,这样如果我们向它提供另一个实现了另一个可选方法的委托,我们的对象看起来就像它有它们太

尝试#1 (NSProxy)

首先,我们从一个通用的SOMulticastProxy开始,这是一种代理,它将调用委托给两个对象(请参阅帮助程序SOOptionallyRetainHolder的来源)。

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基本上遵循:找到响应所需选择器的第一个委托并在那里转发呼叫。如果两位代表都不知道选择者 - 说我们不知道它。这比委托所有方法的自动化更强大,因为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会保留其对象,在典型的使用场景中,您希望保留第一个代理而不保留第二个代理(感谢Giuseppe Lanza提到我最初完全忘记的这个方面)

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。我已经完成了一次并且它有效但我不建议这样做,除非你有充分的理由去做。

答案 2 :(得分:0)

你可以做这样的事情。撰写class B的人使用extension A添加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]
    }
}

答案 3 :(得分:0)

我认为最好的方法是在ClassA中继承UIViewController并实现UITableViewDataSource。 要阻止调用ClassA中实现的必需方法,只需将final关键字放在func实现中。

这是我的解决方案:

<强> 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)"
    }
}

这是你得到的:

enter image description here

答案 4 :(得分:-1)

正如其他人提到的,我们需要一个代理。要设计一个在保留周期方面安全的代理,这很容易。为了使其具有通用性和灵活性,这是一个完全不同的故事。 这里的每个人都知道委托模式要求委托对象较弱以避免保留周期(保留b和b保留一个,因此没有人被释放)。

立即解决方案是在您的代理中使用N个变量当然很弱,以便您可以转发给委托调用的那些对象

$form->field($pres_rules[$i], 'time', ['inputOptions' => ['id' => 'time-'.$i]])->widget(TimePicker::className(), [
                    'pluginOptions' => [
                        'id' => 'time-' . $i,
                        'showMeridian' => false,
                        'defaultTime' => '12:00'
                    ],
                    'addonOptions' => [
                        'asButton' => true,
                    ],
                    'containerOptions' => [
                        'id' => 'time-' . $i,
                    ], 
                    'id' => 'time-'.$i
                ], ['id' => 'time-'.$i,]);

这当然会奏效。但它根本不灵活。你可以只有两个代表,如果你想要更多,你必须添加delegate3 var,记得更新所有方法等等。

有人可能会想“很好,我们有一个代表阵列”......错了。该阵列将保留不再弱的代表,我们将有一个保留周期。

解决方案

为了灵活处理,我创建了一个弱集合。 此代码将允许您使用泛型具有一组弱元素。您将能够实现任意数量的代理,这些代理可以容纳您喜欢的任意数量的代理。

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)
    }
}

这将允许您执行以下操作:

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()
        }
    }
}

正如您所看到的,这几行是安全的,因为weakCollection将存储对委托的弱引用,它将在找到释放的委托时清理自己,并且它可以包含协议的对象,使其具有超级灵活性和弯曲性满足您的需求。