Using Delegates on Generic Protocol

Using delegates on generic protocol

This is not possible to use generic protocols. Use concrete type or usual protocol instead. You have to declare concrete T outside of those abstractions

For instance, this should work:

class MyType<T> {

weak var delegate: UsingGenericProtocol<T>? // First error

var t: T

init(t: T) {
self.t = t
}

func finished() {
delegate?.funca(component: t) // Second error
}

}
class UsingGenericProtocol<T>: GenericProtocol {
let myType: MyType<T>
typealias type = T

init(t: T) {
myType = MyType<T>(t: t)
}

func funca(component: T) {

}
}

let instance = UsingGenericProtocol<Int>(t: 0)

How to use a generic protocol as a delegate?

For protocols that have associated types, the compiler needs to know exactly what type the conforming object is.
To make that happen, we can either use a conforming concrete type directly or use the protocol as a generic constraint. The actual types that are being referred to won’t be known unless we provide some additional context.
So you can give the FirebaseService a concrete type:

weak var delegate: ChildViewController?

That will work but maybe this is not what you want.
Please read this excellent article by John Sundell:
Why can’t certain protocols, like Equatable and Hashable, be referenced directly?

Swift delegate for a generic class

It is hard to know what the best solution is to your problem without having more information, but one possible solution is to change your protocol declaration to this:

protocol MyClassDelegate: class {
func valueChanged<T>(genericClass: MyClass<T>)
}

That removes the need for a typealias in the protocol and should resolve the error messages that you've been getting.

Part of the reason why I'm not sure if this is the best solution for you is because I don't know how or where the valueChanged function is called, and so I don't know if it is practical to add a generic parameter to that function. If this solution doesn't work, post a comment.

Using associatedtype in a delegate protocol for a generic type

You could remove the associated type requirement from your protocol and use a generic function game instead:

protocol GamePointsDelegate {
func game<B>(_ game: Game<B>, didSetPoints points: Int)
}

So you can use the code of your Game class as it is but the downside is that the class which conforms to the protocol has to handle all Boards.

Implement delegate using generic protocol in Swift

Your didReceiveAPIResults declaration in APIControllerProtocol needs to be a generic function so that the generic type T is passed along to it correctly.

protocol APIControllerProtocol {
typealias T

func didReceiveAPIResults<T>(results: [T])
}

Note: This means your delegate definition will need to define what T is:

class TestDelegate: APIControllerProtocol {
typealias T = Album

func didReceiveAPIResults<T>(results: [T]) {
// ...
}
}

Update: While the code above does get rid of the original error, it turns out that it acts more like a workaround and doesn't really address the root of the problem.

The real issue seems to be that the compiler is having trouble reconciling what U.T is with no ambiguity. That's actually easy enough to fix though, we just need to give it a more precise definition (note the where clause in the APIController definition):

protocol APIControllerProtocol {
typealias T
func didReceiveAPIResults(results: [T])
}

class APIController<U:APIControllerProtocol where U.T == Album> {
typealias ElementType = U
// ...
}

Note: I removed the <T> that I added to the function in the protocol previously; that's not needed anymore and will end up causing problems later.

With that, the TestDelegate class works as expected (you don't even need the typealias anymore):

class TestDelegate: APIControllerProtocol {
var albums: [Album]? = nil

func didReceiveAPIResults(results: [Album]) {
albums = results
}
}

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

Your mistake is in the protocol:

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

This says that in order to be a GTCD, a type must accept any type T passed to this function. But you don't mean that. You meant this:

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

And then you wanted the delegate's DataType to match the table view's DataType. And that gets us into the world of PATs (protocols with associated types), type erasers, and generalized existentials (which don't exist yet in Swift), and really it just gets to be a mess.

While this is a use case that generalized existentials are particularly well suited for (if they're ever added to Swift), in a lot of cases you probably don't want this anyway. The delegation pattern is an ObjC pattern developed before the addition of closures. It used to be very hard to pass functions around in ObjC, so even very simple callbacks were turned into delegates. In most cases, I think Richard Topchiy's approach is exactly right. Just pass a function.

But what if you really want to keep the delegate style? We can (almost) do that. The one glitch is that you can't have a property called delegate. You can set it, but you can't fetch it.

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

This is a useful technique to understand, but I probably wouldn't use it in most cases. There's definitely a headache as you add more methods, if those methods reference DataType. You require a lot of boilerplate. Note that there's a bit of messiness due to passing self to the delegate method. That's something delegate methods need, but closures do not (you can always capture the controller in the closure if the closure needs it).

As you explore this kind of reusable code, I encourage you to think more about encapsulating strategies rather than about objects and delegate protocols. An example of encapsulating a strategy would be to have a SelectionHandler type that you hand to the controller:

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

With that, you can build simple strategies like "print it out:"

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

Or more interestingly, update a label:

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

So then you get code like:

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

Or, even more interestingly, you can build higher-order types:

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

This approach composes much more powerfully than delegation, and starts to unlock the real advantages of Swift.



Related Topics



Leave a reply



Submit