Swift:使用“ where”子句符合通用方法协议

时间:2019-05-31 06:50:05

标签: ios swift generics protocols swift-protocols

摘要:

我想创建一个Class<T>,它将具有一个带有ClassDelegate的相应func<T>协议。

目标:

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

示例代码:

具有通用方法的协议:

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

通用基础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)
    }
}

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

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

是否有一种方法可以使用通用方法,并且可以在该方法的实现中使用具体的(专用)类型?

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

screenshot

3 个答案:

答案 0 :(得分:3)

您的错误出在协议中:

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

然后您希望委托的数据类型与表视图的数据类型匹配。这就使我们进入了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: $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)
    }
}

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

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

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

有了它,您可以建立简单的策略,例如“打印出来:”

extension SelectionHandler {
    static func printSelection() -> SelectionHandler {
        return SelectionHandler { print($0) }
    }
}

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

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

因此,您将获得如下代码:

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

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

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

与委托相比,这种方法的功能要强大得多,并开始释放Swift的真正优势。

答案 1 :(得分:1)

On of the possible ways to resolve this situation is to use callbacks instead of delegation. By passing not a closure but a instance method it looks almost identical to the delegation pattern:

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

    }
}

As a benefit, the code is pretty straightforward and doesn't involve any advanced workarounds for Swift's type system which often has some issues when dealing with generics and protocols.

答案 2 :(得分:0)

在您想要的某种意义上,我认为这不可行。最接近的将是与子类结合。请考虑以下内容:

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

}

问题在于2个泛型之间没有关联。因此,必须在某个时候进行转换。在这里,我们使用协议方法进行操作:

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。什么都没有停止编译器,但是如果这样做了,错误将是什么呢?

无论如何,只要您正确使用它,它就可以工作。如果您不这样做,它将崩溃。您可以选择打开一个包装,但不确定是否合适。