Swift:符合带有 "where" 子句的通用方法的协议

Swift: conformance to the Protocol with Generic method with "where" clause

总结:

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

目标:

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

示例代码:

具有通用方法的协议:

protocol GenericTableControllerDelegate: AnyObject {
    func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}

通用基础 UITableViewController subclass:

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

GenericTableController的特殊版本:

final class SpecializedTableController: GenericTableController<NSObject> {}

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

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

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

Type 'AnotherClientOfTableController' does not conform to protocol 'GenericTableControllerDelegate' Do you want to add protocol stubs?

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

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

我不认为这在您想要的意义上是可行的。最接近的是与子类结合。考虑以下因素:

protocol MagicProtocol {
    func dooMagic<T>(_ trick: T)
}

class Magician<TrickType> {
    private let listener: MagicProtocol
    private let tricks: [TrickType]
    init(listener: MagicProtocol, tricks: [TrickType]) { self.listener = listener; self.tricks = tricks }
    func abracadabra() { listener.dooMagic(tricks.randomElement()) }
}

class Audience<DataType>: MagicProtocol {

    var magician: Magician<DataType>?

    init() {
        magician?.abracadabra()
    }

    func doExplicitMagic(_ trick: DataType) {

    }

    func dooMagic<T>(_ trick: T) {
        doExplicitMagic(trick as! DataType)
    }

}

现在我可以创建一个子类并将其限制为某种类型:

class IntegerAudience: Audience<Int> {

    override func doExplicitMagic(_ trick: Int) {
        print("This works")
    }

}

问题是这两个泛型之间没有相关性。所以在某些时候必须进行演员表。这里我们在协议方法中进行:

doExplicitMagic(trick as! DataType)

看起来这很安全,它永远不会崩溃,但如果你仔细观察,我们可以这样做:

func makeThingsGoWrong() {
    let myAudience = IntegerAudience()
    let evilMagician = Magician(listener: myAudience, tricks: ["Time to burn"])
    evilMagician.abracadabra() // This should crash the app
}

此处myAudience对应协议MagicProtocol,可能不限于通用。但是 myAudience 仅限于 Int。没有什么能阻止编译器,但如果它停止了,错误会是什么?

总之,只要你用对了,它就可以用。如果你不这样做,它就会崩溃。你可以做一个可选的解包,但我不确定它是否合适。

解决这种情况的一种可能方法是使用回调而不是委托。通过不传递闭包而是传递实例方法,它看起来与委托模式几乎相同:

open class GenericTableController2<DataType>: UITableViewController {
    var onSelect: ((DataType) -> Void)?
    var data = [DataType]()

    open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let item = data[indexPath.row]
        onSelect?(item)
    }
}

final class CallbackExample: GenericTableController2<NSObject> {
}

final class CallBackClient: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let vc = CallbackExample()
        vc.onSelect = handleSelection
    }

    func handleSelection(_ object: NSObject) {

    }
}

作为一个好处,代码非常简单,不涉及 Swift 类型系统的任何高级解决方法,后者在处理泛型和协议时经常会遇到一些问题。

你的错误在协议中:

protocol GenericTableControllerDelegate: AnyObject {
    func controller<T>(controller: GenericTableController<T>, didSelect value: T)
}

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

public protocol GenericTableControllerDelegate: AnyObject {
    associatedtype DataType
    func controller(controller: GenericTableController<DataType>, didSelect value: DataType)
}

然后您希望委托的数据类型与 table 视图的数据类型相匹配。这让我们进入了 PAT(具有关联类型的协议)、类型橡皮擦和 generalized existentials(在 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: [=12=]) }
                }
            } 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)
    }
}

这是一个需要理解的有用技术,但在大多数情况下我可能不会使用它。当您添加更多方法时,如果这些方法引用 DataType,肯定会让人头疼。你需要很多样板。请注意,由于将 self 传递给委托方法,因此有些混乱。这是委托方法需要的东西,但闭包不需要(如果闭包需要它,您总是可以在闭包中捕获控制器)。

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

struct SelectionHandler<Element> {
    let didSelect: (Element) -> Void
}

有了它,您可以构建简单的策略,例如 "print it out:"

extension SelectionHandler {
    static func printSelection() -> SelectionHandler {
        return SelectionHandler { print([=14=]) }
    }
}

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

static func update(label: UILabel) -> SelectionHandler {
    return SelectionHandler { [weak label] in label?.text = "\([=15=])" }
}

然后你得到如下代码:

controller.selectionHandler = .update(label: self.nameLabel)

或者,更有趣的是,您可以构建 higher-order 类型:

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

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

controller.selectionHandler = .trace(.update(label: self.nameLabel))

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