Swift:遵守带有“where”子句的通用方法的协议

Ric*_*hiy 5 generics protocols ios swift swift-protocols

概括:

我想创建一个其中Class<T>包含相应ClassDelegate协议的协议func<T>

目标:

通过多个对象类重用单个对象和行为。接收已有专门类的委托回调,无需将对象转换为特定类即可使用它。

示例代码:

具有通用方法的协议:

protocol GenericTableControllerDelegate: AnyObject {
    func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}
Run Code Online (Sandbox Code Playgroud)

通用基子类UITableViewController

open class GenericTableController<DataType>: UITableViewController {
    weak var delegate: GenericTableControllerDelegate?
    var data = [DataType]()

    open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.row]
        delegate?.controller(controller: self, didSelect: item)
    }
}
Run Code Online (Sandbox Code Playgroud)

的专门版本GenericTableController

final class SpecializedTableController: GenericTableController<NSObject> {}
Run Code Online (Sandbox Code Playgroud)

-的客户端SpecializedTableController实现了结果,但需要类型转换才能访问专用数据类型:

final class ClientOfTableController: UIViewController, GenericTableControllerDelegate {
    // Works OK
    func controller<T>(controller: GenericTableController<T>, didSelect value: T) {
        if let value = value as? NSObject {
            // Requires unwrapping and casting
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

的客户端SpecializedTableController,具有“where”要求 - 唯一的问题是它无法编译

final class AnotherClientOfTableController: UIViewController, GenericTableControllerDelegate {
    // Works OK
    func controller<T>(controller: GenericTableController<T>, didSelect value: T) where T: NSObject {
        // `value` is String
    }    
}
Run Code Online (Sandbox Code Playgroud)

类型“AnotherClientOfTableController”不符合协议“GenericTableControllerDelegate”是否要添加协议存根?

有没有一种方法可以拥有一个具有通用方法的协议,并且能够在该方法实现中拥有具体(专用)类型?

是否有接近的替代方案可以满足类似的要求(具有通用类但能够在委托回调中处理具体类型)?

截屏

Rob*_*ier 5

你的错误在协议中:

protocol GenericTableControllerDelegate: AnyObject {
    func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}
Run Code Online (Sandbox Code Playgroud)

这表示,为了成为 GTCD,类型必须接受传递给该函数的任何类型T。但你不是这个意思。你的意思是这样的:

public protocol GenericTableControllerDelegate: AnyObject {
    associatedtype DataType
    func controller(controller: GenericTableController<DataType>, didSelect value: DataType)
}
Run Code Online (Sandbox Code Playgroud)

然后您希望委托的数据类型与表视图的数据类型匹配。这让我们进入了 PAT(具有关联类型的协议)、类型擦除器和广义存在主义(Swift 中尚不存在)的世界,实际上它变得一团糟。

虽然这是广义存在主义特别适合的用例(如果它们曾经添加到 Swift 中),但在很多情况下您可能不希望这样。委托模式是在添加闭包之前开发的 ObjC 模式。过去在 ObjC 中传递函数非常困难,因此即使是非常简单的回调也会变成委托。在大多数情况下,我认为 Richard Topchiy 的方法是完全正确的。只需传递一个函数即可。

但是如果您确实想保持委托风格怎么办?我们(几乎)可以做到这一点。一个小问题是你不能拥有名为 的属性delegate。您可以设置它,但无法获取它。

open class GenericTableController<DataType>: UITableViewController
{
    // This is the function to actually call
    private var didSelect: ((DataType) -> Void)?

    // We can set the delegate using any implementer of the protocol
    // But it has to be called `controller.setDelegate(self)`.
    public func setDelegate<Delegate: GenericTableControllerDelegate>(_ d: Delegate?)
        where Delegate.DataType == DataType {
            if let d = d {
                didSelect = { [weak d, weak self] in
                    if let self = self { d?.controller(controller: self, didSelect: $0) }
                }
            } else {
                didSelect = nil
            }
    }

    var data = [DataType]()

    // and here, just call our internal method
    open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.row]
        didSelect?(item)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一种易于理解的有用技术,但在大多数情况下我可能不会使用它。当你添加更多的方法时,如果这些方法引用了 DataType,那肯定会让人头疼。你需要大量的样板文件。self请注意,由于传递给委托方法,会出现一些混乱。这是委托方法所需要的,但闭包不需要(如果闭包需要的话,您始终可以在闭包中捕获控制器)。

当您探索这种可重用代码时,我鼓励您更多地考虑封装策略,而不是对象和委托协议。封装策略的一个示例是拥有一个传递给控制器​​的 SelectionHandler 类型:

struct SelectionHandler<Element> {
    let didSelect: (Element) -> Void
}
Run Code Online (Sandbox Code Playgroud)

这样,您就可以构建简单的策略,例如“打印出来:”

extension SelectionHandler {
    static func printSelection() -> SelectionHandler {
        return SelectionHandler { print($0) }
    }
}
Run Code Online (Sandbox Code Playgroud)

或者更有趣的是,更新标签:

static func update(label: UILabel) -> SelectionHandler {
    return SelectionHandler { [weak label] in label?.text = "\($0)" }
}
Run Code Online (Sandbox Code Playgroud)

那么你会得到如下代码:

controller.selectionHandler = .update(label: self.nameLabel)
Run Code Online (Sandbox Code Playgroud)

或者,更有趣的是,您可以构建高阶类型:

static func combine(_ handlers: [SelectionHandler]) -> SelectionHandler {
    return SelectionHandler {
        for handler in handlers {
            handler.didSelect($0)
        }
    }
}

static func trace(_ handler: SelectionHandler) -> SelectionHandler {
    return .combine([.printSelection(), handler])
}

controller.selectionHandler = .trace(.update(label: self.nameLabel))
Run Code Online (Sandbox Code Playgroud)

这种方法比委托更强大,并开始释放 Swift 的真正优势。